Let's Connect

Software engineer and tech tinkerer. I love building cool things and learning new stuff.

15 Posts
5 Categories
7 Tags
Topics
Blog
Swift Any & AnyObject Types
+

Bu yazımda şu konuları anlatmaya çalışacağım:

  • Any nedir? Hangi amaçla kullanılmaktadır?
  • Any & AnyObject Kullanım Alanları
  • Swift compiler arka planda neler yapıyor?
  • Any türünden bir değişkenin doğru kullanımı
  • Any bir referans mıdır? Compiler burada nasıl davranır?
  • Any türden diziler tanımlayabilir miyiz?
  • Any ile AnyObject arasında ki fark nedir?

Swift dili geliştirilirken birçok programlama dilinden yararlanılmış. Diğer dillerin dizaynından, sentaks yapısından, iyi yönlerinden faydanılarak oluşturulmuş.

Swift programlama dilinin multi paradigm olduğunu söylemiştik. Yani prosedural, object oriented, functional programlama teknikleri ile uygulama geliştirebiliyoruz.

Java, C# programlama dillerinde herşey en üst sınıf Object’ten oluşmaktadır. C++ dilinde ise böyle bir teknik yoktur. Swift dili burada C++ diline benzemektedir.


Any nedir? Hangi amaçla kullanılmaktadır?

  • Any öncelikle bir türdür.
  • Herhangi türden bir nesneye Any isimli bir türe atayabiliriz.
  • Any türünden nesne yaratamayız. Burada ki çözüm yolu referans bildirmektir çünkü her türden nesne any türünden referansa atanabilir. Hemen örneğe bakalım:
var ival: Int = 10
var dval: Double = 9.9
var sval: String = "Kerem"

struct ProgLang{
    // KODLAR
}

class FavLang{
    // KODLAR
}


var anyTypes: Any

anyTypes = ival
anyTypes = dval
anyTypes = sval
anyTypes = ProgLang()
anyTypes = FavLang()

Kodu derlediğimiz zaman hiçbir hata almadığınızı göreceksiniz. Burada dikkatinizi çekmek istediğim bir nokta var. anyTypes isimli Any türüne referans haricinde(class), struct değişkenleride atayabiliyoruz.


Swift compiler bu işlemi nasıl yapıyor?

  • Any türüne bir yapı veya enum atandığı zaman “boxing conversion” yoluyla HEAP alanında bir kopyasını çıkarır.
  • Any referansı artık HEAP alanında ki o kopya değeri gösterir.

Senaryo: Parametresi Any türünden olan bir fonksiyon yazalım. Bu fonksiyonu herhangi türden bir değer ile çağıralım.

var ival: Int = 10
var dval: Double = 9.9
var sval: String = "Kerem"

struct Lispic {
    // ...
}

class LearnSwift {
    // ...
}

func anyType(herhangiBirTip x: Any) {
    print(type(of: x))
}

anyType(herhangiBirTip: ival)   // Int
anyType(herhangiBirTip: dval)   // Double
anyType(herhangiBirTip: sval)   // String
anyType(herhangiBirTip: Lispic())   // Lispic
anyType(herhangiBirTip: LearnSwift())   // LearnSwift

Sonuç: Fonksiyonlar Any türünden olabilir ve herhangi türden bir değişken ile çağırabiliriz.


Any türünden bir değişkenin kullanımı

  • Tür dönüşümü yapmadan kullanmamalıyız.
  • Tür dönüşümünü as!(Forced downcasting) yada as?(Conditional downcasting) operatorlerini kullanarak yapabiliriz.

Senaryo: Bir User sınıfı tanımlayalım. Sınıf isim, yaş ve favori programlama dili özelliklerinden oluşsun. Sınıfın display diye bir fonksiyonu olsun. Biz Any türden bir değişkene oluşturduğumuz sınıfı atıp, display fonksiyonuna erişmeye çalışalım.

class User {
    var name: String
    var age: Int
    var favProgLang: String

    init(name: String, age: Int, favProgLang: String){
        self.name = name
        self.age = age
        self.favProgLang = favProgLang
    }

    func display(){
        print("Isim -> \(self.name)\nYas -> \(self.age)\nFavori Programlama Dili -> \(self.favProgLang)")
    }
}


var anyType: Any
anyType = User(name: "Kerem", age: 28, favProgLang: "Lisp")
anyType.display() // Value of type 'Any' has no member 'display'

var anyType: Any
anyType = User(name: "Kerem", age: 28, favProgLang: "Lisp")
var res = anyType as! User
res.display()

Sonuç: Hatadan anladığımız gibi Any türünden bir değişkeni direk olarak işleme sokamıyoruz. Tür dönüşümü yapmamız gerekiyor.


Any bir referans mıdır? Compiler burada nasıl davranır?

  • Any bir referanstır.
  • Swift dilinde bir referans HEAP alanında ki bir nesneyi gösterir.
  • Referans ise stack alanında yer alır. Yani bir yapıyı any türünden bir refaransa atadığımızda boxing conversion gerçekleşir. Yukarıda da dediğimiz gibi swift compiler HEAP’te yapı değişkeninin bir kopyasını oluşturur.
  • Any referansı Stack alanındadır ve HEAP alanında ki nesneyi gösterir hale gelir.

Bu söylediğimizi ispatlayan ufak bir kod yazalım.

func printAddress<T>(of pointer: UnsafePointer<T>) {
    print(pointer)
}

var ival: Int = 3
printAddress(of: &ival) // 0x00000001003edec0
var anyType: Any

anyType = ival
printAddress(of: &anyType) // 0x00000001003edec8
// anyType ival nesnesini gostermiyor, ival nesnesinin kopyasini gosteriyor
// Hemen ival nesnesinde degisiklik yapiyorum
ival = 999

// Eger anyType ival nesnesini gosteriyorsa degeri 999 olmasi gerekiyor
print(anyType as! Int) // 3 degerini gosteriyor

Sonuç: Bellek adreslerinde de anlaşıldığı gibi aynı nesneyi değil, kopyasını göstermektedir.


Any türden diziler tanımlayabilir miyiz?

  • Any türünden diziler tanımlayabiliriz.
  • Dizileri dolaşırken dikkat etmemiz gereken nokta is operatörü ile dizi elemanlarının türlerini belirlememiz gerekiyor.
  • İstenilirse Any? türünden de referanslar tanımlayabiliriz. Bu sayede referanslara nil atayabiliriz.

Any ile AnyObject arasında ki fark nedir?

Any & AnyObject

Yukarıda ki grafikte herşey çok açık duruyor. AnyObject türüne referans türlerine ilişkin nesneler atayabiliyoruz. Any türüne ise her türden nesne atayabiliyoruz.


Kaynaklar:

How to share structs using boxing?

Type casting in swift : difference between is, as, as?, as!

Swift Type Casting – as, is, Any, AnyObject

Mastering Swift: Tips About Array and Dictionary Literals

What Is Any in Swift

Any Object

Swift Generics
+

Swift Generics

Bu yazımda şu konuları anlatmaya çalışacağım:

  • Generic nedir?
  • Genericlere neden ihtiyaç var?
  • Generic bir fonksiyon tanımı nasıl yapılır?
  • Genericler sadece fonksiyonlarla mı tanımlanabilir?
  • Swift Generic Type Constraints
  • Generic Protocol Kavramı
  • Generic protocol nasıl oluştururuz?

Generic nedir?

  • Türden bağımsız işlem yapabilmeyi sağlayan mekanizmadır.
  • C++ dilinde template olarak geçmektedir. Java ve C# dillerinde generic olarak eklenmiştir.
  • Swift dilinde ki generic yapılar daha fazla C++ dilinde ki template mekanizma işleyişine benzemektedir.

Genericlere neden ihtiyaç duyuyoruz?

  • Bazı senaryolarda farklı türler için aynı işi yapan birden fazla fonksiyon yada metodun yazılması gerekebilir. Tüm bu fonksiyonları tek tek yazmamak için biz generic mekanizmasını kullanıp bir tane şablon yazarız ve derleyici bizim yerimize türden bağımsız, diğer türler için gerekli işlemleri yapar.

Senaryo: Parametre olarak liste alan bir fonksiyon geriye listenin en büyük elemanını döndürsün.

func getMax(dizi items: [Int]) -> Int
{
    var max = items[0]

    for number in items {
        if max < number{
            max = number
        }
    }
    return max
}

let sayiDizisi = [97, 34, 1, 129, 444]
getMax(dizi: sayiDizisi)    // 444
let sayiDizisi2 = [97.0, 34.34, 1.12, 129.33, 97.34, 129.34]
getMax(dizi: sayiDizisi2)  // HATA
// Cannot convert value of type '[Double]' to expected argument type '[Int]'
  • Yazdığımız fonksiyon Int dizisi için çalışıyor ama bizim senaryomuzda böyle bir kısıtlama yok. Yani Double dizide gönderseler sonucu doğru şekilde görmeliyiz. Swift dilinde bunu yapabilmek için Double içinde ayrı bir fonksiyon yazmalıyız. İşte burada her tür için fonksiyon yazmak yerine GENERIC konusu gelip bizi kurtarıyor. Ve bu kısmı SWIFT COMPILER bizim için hallediyor.

Generic bir fonksiyon tanımı nasıl yapılır?

  • Swift dilinde generic fonksiyon tanımı, fonksiyon isminden sonra <> gibi açısal parantezler arasında tür parametrelerinin bildirilmesiyle yapılır. Örneğin
func genericExam<T, K, M>(tmp1: T, tmp2: K, tmp3: M) -> T
{
  // KODLAR
}

Yukarıda ki kodda gördüğümüz T, K, M herhangi bir tür parametresi. Buraya istediğiniz harflendirmeyi yapabilirsiniz. Genel olarak bu isimlendirmelerde tek harf kullanılır ama bu bir kural değil tercihtir.

Senaryo: Bir swap fonksiyonunu generic kullanarak yazalım.

func swap<T>(item1: inout T, item2: inout T)
{
    let tmp = item1
    item1 = item2
    item2 = tmp
}

var name: String = "Kerem", surname: String = "Vatandas"
swap(&name, &surname)
print("isim: \(name), soyisim: \(surname)") // "isim: Vatandas, soyisim: Kerem\n"
var no1: Int = 99, no2: Int = 33
swap(&no1, &no2)
print("no1: \(no1), no2: \(no1)")  // "no1: 33, no2: 33\n"

var dNo1: Double = 12.3, dNo2: Double = 20.5
swap(&dNo1, &dNo2)
print("dNo1 = \(dNo1), dNo2 = \(dNo2)")  // "dNo1 = 20.5, dNo2 = 12.3\n"

Sonuç: Swap fonksiyonunu tanımlarken T herhangi bir tür oluyor. Burada derleyici argümanlara bakarak türü otomatik tespit ediyor. Örneğin fonksiyon çağırılırken parametreler string olduğu için derleyici T türünün String olduğunu anlıyor.

  • Swift dilinde C++ dilinde olduğu gibi generic türlerin açıkça belirtilmesi özelliği yoktur. Bu nedenle swift dilinde tüm generic tür parametrelerinin fonksiyon imzası içerisinde kullanılması zorunludur. Söylemek istediğimi kod üzerinde göstereyim.
func exam1<T>(){}   // HATA
func exam2<Double>()   // C++ bu ozellik var, Swift dilinde yok

Genericler sadece fonksiyonlarla mı tanımlanabilir?

  • Tabi ki hayır. Generic class, struct, enum tanımlanabilir. Önemli nokta swift dilinde prokoller GENERİC OLAMAZ.
  • Generic sınıf, yapı, enum tanımlamak, fonksiyon tanımlamakla aynı yapıyı kullanıyor. Örneğin
class AlgoList<T>
{
   // KODLAR
}

Senaryo: Bir stack veri yapısını generic mekanizması kullanarak yazalım.

struct Stack<T> {
    private var stack: [T]

    init()
    {
        stack = [T]()
    }

    mutating func push(val: T)
    {
        stack.append(val)
    }

    mutating func pop() -> T
    {
        return stack.removeLast()
    }

    var isEmpty: Bool {
        return stack.isEmpty
    }

    var count : Int {
        return stack.count
    }
}

// Int icin
var stackInt = Stack<Int>()

stackInt.push(val: 10)
stackInt.push(val: 20)
stackInt.push(val: 30)

while !stackInt.isEmpty {
    print(stackInt.pop())
}

// String Icin
var stackString = Stack<String>()

stackString.push(val: "Kerem")
stackString.push(val: "Swift")
stackString.push(val: "Python")

while !stackString.isEmpty {
    print(stackString.pop())
}

Swift Generic Type Constraints

  • Bir generic tanımında tür parametreleri hangi türden açılırsa açılsın anlamla olmak zorundadır. Aksi durumda compile time’da hata oluşur. Ne demek istediğimizi hemen bir örnek üzerinden açıklayım.

func findIndex<T>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}
// Binary operator '==' cannot be applied to two 'T' operands
let stringList = ["Python", "C++", "Swift", "Lisp", "Lua"]

if let result = findIndex(of: "Swift", in: stringList) {
    print(result)
}
else {
    print("Bulunamadi")
}

Evet burada bir problem var. Şöyle bir hata alıyoruz.

// Binary operator ‘==’ cannot be applied to two ‘T’ operands

Burada her T türünün == operator fonksiyonu olmak zorunda değil. İşte bu tür durumlarda generic parametrelerine bazı kısıtları sağlamak zorunluluğu getirebiliriz.


Swift dilinde type constraints nasıl yapıyoruz?

<turParametresi> : <ProtocolListesi>
  • Bu şekilde gene anlaşılmamış olabilir. Yukarıda yazdığımız fonksiyon parçamıza ekleyelim.
func findIndex<T: Equatable>(of valueToFind: T, in array:[T]) -> Int? {
    for (index, value) in array.enumerated() {
        if value == valueToFind {
            return index
        }
    }
    return nil
}

let stringList = ["Python", "C++", "Swift", "Lisp", "Lua"]

if let result = findIndex(of: "Swift", in: stringList) {
    print(result)   // 2
}
else {
    print("Bulunamadi")
}

Peki burada neler oldu?

  • Burada derleyici T türünün Equatable protokolünü destekleyen bir türle açılacağı GARANTİSİ verildi. Artık fonksiyon Equatable protokolünü desteklemeyen bir türle çağırmaya çalışırsak derleme aşamasında error oluşur.
  • Equatable protokolü ==, != destekliyor. Bunların dışında başka bir operatör kullanalım. Koda göre çok mantıklı olmasa da sadece hatayı görmek için <= kullanacağım. Alacağımız derleyici hatasını görmek için.
// KODLAR
// Binary operator '<=' cannot be applied to two 'T' operands
if value <= valueToFind {
    return index
}
//KODLAR
  • Şimdi yukarıda verdiğimiz formüle, yazım kuralanı göre : vardı. Tabi burada protocol konusunuda bilmemiz gerekiyor. Equatable prokolu sadece == ve != destekliyordu.

Yukarıda ki hatayı kaldırabilmek için ne yapabiliriz?

  • Comparable protokolü Equatable protokolünden türetilmiştir. Bu protokolde yalnızca < operatör fonksiyonu vardır.
  • Yani Comparable protokolünü destekleyen bir tür hem == hem de < operatör fonksiyonlarını bulundurmak zorundadır.
  • Bu iki operatör fonksiyonu bulunduğunda kütüphanedeki !=, <=, >= ve > generic operatör metotları devreye girerek != <=, >= ve > işlemlerini == ve < operatörlerini kullanarak yapmaktadır.
  • Dolayısıyla bir tür Comparable protokolünü destekliyorsa biz o turu 6 karşılaştırma operatörüyle de işleme sokabiliriz. Kafamız karışmış olabilir. Hemen yukarıda ilk tanımladığımız getMax fonksiyonu üzerinden örnek verelim.
func getMax<T: Comparable>(dizi a: [T]) -> T
{
    var max = a[0]

    for i in 1..<a.count {
        if max < a[i] {
            max = a[i]
        }
    }

    return max
}

let sayiDizisi = [97, 34, 1, 129, 444]
getMax(dizi: sayiDizisi)    // 444
let sayiDizisi2 = [97.0, 97.12, 1, 97.88, 97.9]
getMax(dizi: sayiDizisi2)    // 97.90000000000001

Swift dilinde generic protocol kavramı var mı?

  • Swift dilinde generic protocol kavramı yok. Ama dolaylı yollardan oluşturabiliyoruz.

Generic protocol nasıl oluştururuz?

  • Swift dilinde associatedtype adında anahtar sözcük bulunuyor. Bu anahtar sözcük ile bir tür ismi oluşturursak protokolün elemanlarını buna dayalı olarak oluşturabiliriz. Böyle anlaması gene zor oluyor. Örnek üzerinden gidelim.

protocol P {
    associatedtype T

    func test(item: T) -> T
}

Yukarıda yazdığımız protocol bir tür destekleyecek ise T türü ne olursa olsun, protocol SADECE BİR TANE uygun method bulundurmak zorundadır.

protocol P {
    associatedtype T

    func test(x: T) -> T
}

class Example : P {

    func test(x: Example) -> Example
    {
        return x
    }
}

Kaynaklar:

Generic Docs STL

Swift Programlama Dili Inceleme
+

Merhaba,

2019 hedeflerimden bir tanesi mobil geliştirme alanına(IOS) giriş yapmak olarak belirledim ve bunun sonucunda öğrendiklerimi, notlarımı, araştırmalarımı düzenli bir şekilde paylaşmaya çalışacağım.

Bu ilk yazıda swift diline ve dökümanlarına göz gezdirdim. Kabataslak diğer dillere benzeyen neler var, dilin sentaks yapısı nasıl, mimarı açıdan nasıl gibi sorulara yanıtlar aradım. Tabi dilin tüm özelliklerini bir makale de anlatmak çok zor. Bunun için ileri ki yazılarımda tek tek ayrıntılara gireceğim.


Bu yazıda hangi konulara değiniyorum;

  • Swift Programlama Dili Neden Ortaya Çıktı?
  • Swift Nasıl Bir Programlama Dilidir?
  • Swift Standart Kütüphanesi Neler İçeriyor?
  • Bildirim İşlemleri Nasıl Yapılıyor?
  • Swift Dilinde String ve Character
  • String Interpolation
  • Fonksiyonların Bildirimleri
  • Swift Dilinde Fonksiyon Overload Var mı?
  • Swift dilinde sabitler nelerdir?
  • Optional Türü ve Nil Sabiti
  • Swift Dilinde Tür Dönüşümleri
  • Swift Dilinde Operatörler
  • Swift dilinde Pointer var mı?
  • Swift dilinde bir nesneyi adres yoluyla fonksiyonlara aktarabilir miyiz?
  • Swift Stack ve Heap Kavramları

Swift Programlama Dili Neden Ortaya Çıktı?

  • Bu sorunun cevabını genel olarak ihtiyaç ve gelişen dünyanın, teknolojinin gerisinde kalmamak olarak verebiliriz.

  • IOS ve MacOS sistemlerin ana geliştirme dili Objective C. Fakat Objective C öğrenmenin bir maliyeti var. Objective C, C dili üzerine nesne yönelimli özellikler eklenerek oluşturulmuş bir dil. Diğer birçok nesne yönelimler diller gibi Smaltalk’tan esinlenilmiş. Bu dili öğrenmek için önce C dilinin öğrenilmesi gerekiyor. Bu da öğrenme gerisini yukarı çekiyor.

  • Apple firması bunun sonucunda daha modern ve basit öğrenilebilecek bir programlama dili arayışına girmiş. Bunun sonucunda Swift programlama dili çıkıyor. Swift dilinin geliştirilmesine Chris Lattner tarafından 2010 yılında başlanmış.

Chris Lattner

  • Swift dili yazım kolaylığı ve dizayn olarak diğer birçok programlama dilinden esinlenerek oluşturulmuş. Swift programlama dili Linux, MacOS, IOS platformları destekliyor.

Swift Nasıl Bir Programlama Dilidir?

  • Swift, C++ programlama dili gibi multi paradigm bir programlama dilidir. Bunun anlamı object oriented, functional, procedural programlama modellerini kullanarak uygulamalar geliştirebiliriz.

  • Tabi ki swift Objective C dilinden pek çok özellik almıştır.

  • Swift başta Objective-C olmak üzere Python, Haskell, Java, C# gibi dillerden güçlü gördüğü özellikleri almış. Bu nedenle diğer dillerden swift’e geçenler rahatlıkla bu dili öğrebilirler.

  • Swift açık kaynak bir programlama dilidir. [https://github.com/apple/swift]

  • Swift derlenen bir programla dilidir, gerçek makina kodları üretmektedir. Swift derleyicisi LLVM derleyici geliştirme ortamından faydalanılarak yazılmış. Bu sebeple swift uygulamaları hızlı çalışırlar.

  • Derleyici -> [https://github.com/apple/swift-llvm]

  • Swift ile yazılan kodlar LLDB ile debug edilebilir.

  • Swift Debugger and REPL -> [https://github.com/apple/swift-lldb]


Swift Standart Kütüphanesi Neler İçeriyor?

Swift programlama dilinin standart kütüphanesi sınıflar, protokoller, global fonksiyonlar, yapılar, data türleri bulunmaktadır. [https://developer.apple.com/documentation/swift/swift_standard_library]

Dökümanlarda da görüldüğü gibi çok temel fonksiyonlar ve sınıflar barındırılmaktadır.


Bildirim İşlemleri Nasıl Yapılıyor?

Swift dilinde bir değişken kullanılmadan önce derleyiciye tanıtılması gerekiyor.[Declaration]

Bildirim işlemleri birkaç farklı şekilde yapılabiliyor. [https://docs.swift.org/swift-book/ReferenceManual/Declarations.html]

var <degiskenIsmi> = <ilkDeger>
var <degiskenIsmi>: <tur> = <ilkDeger>
var <degiskenIsmiListesi>: <tur>

let <degikenIsmi> = <ilkDeger>
let <degikenIsmi>: <tur> = <ilkDeger>

Tanımlama işlemleri yaparken <tür> belirtmek zorunda değiliz. Swift dilinde Type Inference yani tür çıkarımı mekanizması bulunmaktadır. Sizin yerinize turu atanan değerden bulacaktır.

Bildirim yaparken tür belirtmiyorsak değişkene İLKDEĞER vermek ZORUNLUDUR.

var no			// HATALI, ilk deger verin
var no2: Int32	// GECERLI
// kodlar devam
var no1, no2: Int	// GECERLI
var no = 3, no2: Int, no3 = 11, w = “Istanbul”    // GECERLI

Bildirimde var ile let arasında önemli bir farklılık vardır.

  • var ile değişken bildirildiğinde sonradan değeri değiştirilebilir.
  • Ancak let ile değişken bildirildiğinde o değişken const olur. Onun değeri bir daha değiştirilemez.

Eğer let ile bildirim yaparken tür belirtirsek aşağıda 1 kere atama yapabiliyoruz. Ama ikinci atamayı kabul etmez.

let dil: String

dil = "Swift"
dil = "C++" // Immutable value 'dil' may only be initialized once

Özel bir sebebi yok ise let ile bildirim yaparken ilkdeğer verin.


Swift Dilinde String ve Character

Swift programlama dilinde karakter tanımlamak diğer dillerde ki gibi tek tırnak, tek karakter gibi olmuyor. Örneğe bakalım;

let s = 'a'  // Single-quoted string literal found, use '"'
let char = "a"
print(type(of: char))  // String
let char1 = "'a'"
print(type(of: char1))  // String

Karakter tanımlama için turu açık olarak Character belirtmemiz gerekiyor

let s: Character = "a"
print(type(of: s)) // Character

Swift dilinde tüm temel türler birer yapıdır.


String Interpolation

String içerisinde \(ifade) olarak yer alan yapıdır. Parantez içerisindeki ifade stringe dönüştürülür. Buna string interpolation denilmektedir.

let multiplier = 3
let message = "\(multiplier) times 2.5 is \(Double(multiplier) * 2.5)"
// message is "3 times 2.5 is 7.5"

Fonksiyonların Bildirimleri

Swift dilinde daha öncede söylediğimiz gibi multi paradigm bir dil. Yani swift dilinde C++ dilinde olduğu gibi bir sınıf içinde, yapıların içerisinde yada global düzeyde yazılabilir. [https://docs.swift.org/swift-book/LanguageGuide/Functions.html]

Swift dilinde fonksiyon bildirimi şu şekilde yapılmaktadır;

func <fonksiyonIsmi>([parametreler]) -> <fonksiyonGeriDonusDegeriTuru>
{
	// fonksiyon govdesi
}

Fonksiyon bildiriminde geri dönüş değeri yok ise -> kullanılmayabilir.

func greet(person: String) {
    print("Hello, \(person)!")
}

greet(person: "Dave")
// Prints "Hello, Dave!"

Fonksiyonun tek bir geri dönüş değeri vardır fakat birden fazla değer geri döndürmenin birden fazla yolu var.

return deyimi hem fonksiyonu sonlandırmak için hem de geri dönüş değerini oluşturmak için kullanılır.

func sumTwoNumber(no1: Int, no1: Int) -> Int
{
    return no1 + no2
}

Fonksiyon geri dönüş değeri yok ise boş return kullanılabilir. return kullanmak zorunda değiliz.

func sumTwoNumber(no1: Int, no2: Int)
{
    print(no1 + no2)

    return
}

Geri dönüş değeri bildirilmiş bir fonksiyonda return kullanmak zorunludur. Geriye return ile bir değer döndürmüyorsak derleyici hata verecektir.

func sumTwoNumber(no1: Int, no1: Int) -> Int	// HATA
{
    // kod
}

Fonksiyonların parametre bildiriminde parametrelerin türleri belirtmek zorunludur.

Fonksiyonların parametre değişkenleri default olarak const durumundadır, yani fonksiyonlar tarafından bunlar değiştirilemez.

func hi(name: String)
{
    name = "Kerem"  // Cannot assign to value: 'name' is a 'let' constant
}

Parametre değişkenini değiştirmek istiyorsak inout keywordunu kullanmak zorundayız.

func hi(name: inout String)
{
    name = "Kerem"
}
  • Fonksiyonların parametre değişkenlerinin hem kendi isimleri hem de etiket isimleri vardır.
  • Etiket isimleri fonksiyon çağırılırken, parametreler fonksiyon gövdesinde kullanılır. Etiketler okunabilirliği arttırmak için var.

Etiket kullanımını şu şekilde yapabiliriz:

func <fonksiyonIsmi> (<etiketIsmi> <degiskenIsmi> : <degiskenTuru>) -> <geriDonusTuru>
{
	// fonksiyon gövdesi
}

func etiket_kullanimi(genislik w: Double, yükseklik h: Double) -> Double
{
    return w * h // Fonksiyon gövdesinde değişken isimleri var
}
  • Swift dilinde iç içe fonksiyon bildirimi yapabiliyoruz.

Swift Dilinde Fonksiyon Overload Var mı?

  • Evet swift dilinde function overloading mekanizması bulunuyor.

Function overloading olabilmesi için aynı isimli fonksiyon ve aşağıda bulunan özelliklerden bir veya birkaçı bulunuyor olmalı.

  • Fonksiyon geri dönüş değerinin farklılığı
  • Parametre sayılarının farklı olması
  • Parametre türlerinin farklılığı
  • Etiket isimlerinin farklılığı

Derleyicinin hangi fonksiyonu çağırdığını anlama mekanizmasını (function overload resolution) C++ üzerinden anlatmıştım. İleri de bu konuyu daha ayrıntılı anlatmaya düşünüyorum. Mekanizmanın nasıl çalıştığını anlamak için su makaleye bakabilirsiniz. [Function Overloading Resolution]


Swift dilinde sabitler nelerdir?

  • Nil Literals
  • String Literals
  • Boolean Literals
  • Integer Literals
  • Floating Point Literals

Optional Türü ve Nil Sabiti

  • Swift dilinde her T türünün T? biçimi vardır. Double Double?, String String? gibi.
  • T? anlamı hem kendi türünden değer tutabilmesi hem de nil sabitini tutabilmesidir. Değişken içerisinde ki değere ulaşabilmek için unwrap etmemiz gerekir. Unwrap etmek için <T!> yani ! operatörü kullanılmaktadır.
  • ? ve ! operatörleri kendisinden önce ki değişkene BİTİŞİK olarak yazılmalı. Yoksa sentaks hatası alırsınız. (Son ek olarak kullanılır.)
  • Swift dilinde T? türü Optional türüne eşittir. Yani
var word: String?
var word: Optional<String>
// iki tanim birbirine esit

Optional<T>
Optional<Wrapped>
// Optional turu standart kutuphane de enum olarak tanimlanmis

[Optinal]


Swift Dilinde Tür Dönüşümleri

  • Swift programlama dilinde otomatik tür dönüştürme yoktur. Otomatik tür dönüştürmelerinde veri kaybı sorunu olabilir. Swift bunun önüne geçmek için dile otomatik tür dönüşümü eklememiş.

Yani Int32 tipinde bir değişkenin değerini direk Int16 türden bir değişkene atayamayız. Type casting işlemlerini yapmamız gerekiyor.

Swift Dilinde Operatörler

Swift dilinde temel operatörler (basic operators) diler dillerde ki C, C++, C#, Java, Python operatörlere çok benzemektedir.

Operatörler genellikle üç biçimde sınıflandırılırlar:

  1. İşlevlerine Göre
  2. Operand Sayılarına Göre
  3. Operatörün Konumuna Göre

Swift dökümanlarında Basic Operators ve Advanced Operator olarak ayrılmış. Advanced operatörler de Bitwise operatörleri yer alıyor.


Swift dilinde Pointer var mı?

  • C++, C# gibi dillerden de alışık olduğumuz gibi türler iki kısma ayrılıyor. Değer türleri ve referans türleri. Eğer değişken doğrudan değerin kendisini tutuyorsa değer türüne ilişkindir. Değerin bulunduğu BELLEK ADRESİNİ tutuyorsa referans türüne ilişkindir.
  • Referans türleri C dilinde ki pointer, C++ dilinde ki referanslar gibidir. Burada bahsedilen yapılar aslında aynı. C dilinde pointer kullanımı zor olduğu için, C++ diline implemente ederken notasyonel bir hile yapılarak referans adını verdikleri yapı oluşuyor. Böylece kullanımı ve anlaşılması daha kolay bir yapı oluşturulmuş oluyor. Aslında arkada planda dönen herşey pointer.
  • Swift dilinde struct, enüm türleri DEĞER türlerine ilişkindir. String, Int, Double türlerinin hepsi aslanda birer yapıdır. Bu sebepten string türden bir değişkenin karakterlerini istersek değiştirebiliriz.
  • Class, Protocols ise referans türlerine ilişkindir.

Swift dilinde bir nesneyi adres yoluyla fonksiyonlara aktarabilir miyiz?

  • Bir nesneyi adres yoluyla da fonksiyonlara aktarmak mümkündür. Bunu inout ile yapiyoruz. Fonksiyonu çağırırken değişken önüne adres alma operatörü & getirilmelidir. Bu konuda en çok verilen örnek swap işlemi.

func swap( sayi1 n1: inout Int,  sayi2  n2: inout Int)
{
    let temp = n1
    n1 = n2
    n2 = temp
}

var n1 = 10, n2 = 20
swap(&n1, &n2)
print("n1 = \(n1), n2 = \(n2)")  // "n1 = 20, n2 = 10\n"

Swift Stack ve Heap Kavramları

  • Bir fonksiyon çağırılmamış ise fonksiyon içinde yerel değişkenler henüz belleğe yüklenmemiş ve yer kaplamıyorlardır.
  • Fonksiyon çağırıldığında değişkenler belleği yüklenir, bir takım işlemler yapılır ve fonksiyon block sonunda bellekten silinirler. Her fonksiyon çağırıldığında genel davranışları böyledir.

Swift Stack Heap

  • Yerel değişkenler ve parametre değişkenleri belleğin STACK bölgesinde oluşturulur. Stack alanı prosese özgüdür. Her thread’in aynı bir stack alanı var.
  • Global değişkenler programın başında bir kere yaratılıp uygulama bitene kadar bellekte kalırlar. Belleğin DATA ve BSS denilen alanında tutulurlar.
  • Bir de process adres alanı içerisinde HEAP alanı vardır. Swift dilinde sınıf türünden nesneleri HEAP bölgesinde yaratılırlar ve bunların bellekten silinmesiyle garbage collector ilgilenir.

Swift programlama dili nesne yönelimli programlama tekniklerini destekler. Bunun ile alakalı fazla ayrıntıya girmiyorum. Bundan sonra gelecek makaleler tek konu üzerine ayrıntılı şekilde olacaktır.

Python Context Manager
+

Bu yazıda hangi konulara değineceğiz?

  • Context manager nedir?
  • With deyiminin kullanımı nasıldır?
  • Neden böyle bir yapıya ihtiyacımız var?
  • Decorator kullanımı kısa hatırlatma
  • Context manager oluşturmanın yolları nelerdir?

Python programlama dilinde kaynak kullanımı ve kaynakların geri iadesi için with deyimi bulunmaktadır. With deyimi bu işlemleri context management protocol’üne uygun olarak yapmaktadır. Yani bizde bu protokole uyarak kendi sınıflarımızı, kodumuzu yazabiliriz.

Python dilini kullanan herkes bir kere de olsa dosya işlemleri yapmış yada en azından okuduğu kodlarda bu yapıyı görmüştür. Genel yapı şöyledir.

# Dosya Aç
# Dosyaya Yaz yada Oku (Yada herhangi bir işlem yap)
# Dosyayı kapat

Context Manager Nedir?

  • Context manager kullanılan kaynakların geri iade edilmesi için ortaya çıkmış bir protokoldür. Bu protokolü kullanabilmemiz için belli kurallara uygun sınıflar, yapılar oluşturmamız gerekir.

  • Context manager protokolüne uygun yazdığımız sınıflarımızı kullanırken de with deyiminden yararlanacağız.


With Deyiminin Kullanımı

  • with deyiminin kullanımını ve bize getirdiği kolaylığı görmek için yukarıda bahsettiğimiz dosya işlemlerini hem with deyimini kullanarak hem de with deyimi olmadan nasıl yazabileceğimize birer örnek verelim.

İlk olarak with deyimini nasıl kullanıyoruz?

with <ifade> as degiskenIsmi:
    <islemler>
with open("dosyaismi.txt", "w") as fw:
    fw.write("with deyimi kullanimi...")

with deyimi olmasaydı bu işlemi güvenli bir şekilde nasıl yapabilirdik?

try:
    # Dosya ac
    file = open('kerem.txt', 'w')
    # Islem yap
    file.write("Lisp dili candır... o_O")
except IOError:
    # Exception IO HATA MESAJI
finally:
    # Dosya kapa
    file.close()
  • Kısaca açıklamak gerekirse temel yapılan hatalardan bir tanesi dosyayı açıp, işlemleri yaptıktan sonra dosyayı kapamayı unutmak büyük bir problem. Çünkü kullandığımız kaynaklar sınırsız değiller. Devamlı kaynak edinip geri iade etmessek işletim sisteminiz size engel olacaktır.

  • Ama bizim yazdığımız kodda finally bloğu her zaman çalışacaktır ve dosya kapama işlemini yapacaktır.

  • with kullanım alanı sadece dosya işlemleri değil elbette. Database bağlantıları, thread kullanım alanlarda lock mekanizmasında çokça kullanılıyor. Bu kısım oluşturmuş olduğunuz dizayna bağlıdır, yani genişletilebilir.


Kaynaklar sınırsız derken ne demek istiyorsunuz?

  • Hemen küçük bir kod ile açıklamaya çalışalım. Kodumuz şu şekilde çalışsın. 100_000 dosya açmaya çalışalım ve bu açtığımız dosyaları kapatmayalım. Bakalım neler olacak?
files = list()

for x in range(100_000):
    files.append(open(f'files/{str(x)}', 'w'))

Kullandığınız işletim sistemine göre kaynak kullanımı değişiklik gösterecektir. Ben şu şekilde bir hata alıyorum. Hata da gördüğünüz gibi OSError

OSError: \[Errno 24\] Too many open files: 'files/4861'

Neden böyle bir yapıya ihtiyacımız var?

  • Bu hataları engellemek için kullandığımız kaynakları iade etmeliyiz. Bunun içinde pythonda context manager protokolü ve with deyimi var.

Context manager oluşturmanın yolları nelerdir?

  • with deyimi ve context manager protokolünün ne olduğunu, neden kullanmamız gerektiğini anladığımıza göre artık nasıl bu protokole uygun sınıflar yazabiliriz, onun üzerinde duralım.

Context manager oluşturmanın 2 yolu var.

  1. Class Kullanmak
  2. contexmanager decorator kullanmak

Context manager protokolüne uygun class oluşturma

Bir sınıfın context manager protokolüne uygun olabilmesi için __enter__ ve __exit__ metotlarını bulundurması gerekiyor.

  • __enter__ metodu self dışında herhangi bir parametre almaz
  • __exit__ metodu self dışında 3 parametre daha alıyor. (type, value, traceback)

Basit bir örnek yazalım ve nasıl çalıştığını anlayalım.

#!/usr/bin/env python3

class Kerem:

    def __init__(self):
        print("init metodu calisti...")

    def __enter__(self):
        print('__enter__ metodu cagirildi...')
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        print('__exit__ metodu cagirildi...')

with Kerem() as f:
    print("with blogunun icindeyim...")
init metodu calisti...
__enter__ metodu cagirildi...
with blogunun icindeyim...
__exit__ metodu cagirildi...

Çalışma akışını gördünüz. Sınıfımızı context manager protokolüne uygun şekilde yazdık ve daha sonra with deyimi ile birlikte kullandık.

  • Önce __init__ çalıştı.
  • İkinci olarak __enter__ metodu içinde ki işlem yapıldı ve işlem sonucu return ile geri dönüldü.
  • with deyimi içerisindeki alan işlendi. Burada bir değişkene return edilen değeri atadık vb işlemler.
  • with bloğu bittiği gibi __exit__ metodu çağırıldı.

Artık çalışma yapısını da biliyoruz. Yukarıda ilk verdiğimiz dosya işlemleri üzerine bir örnek yazalım.

#!/usr/bin/env python3

class File(object):

    # Constructor metodumuzu yazalim
    def __init__(self, file_name, mode):
        self.file_name = file_name
        self.mode = mode

    def __enter__(self):
        # Islemleri yapildigi metod
        self.f_obj = open(self.file_name, self.mode)
        return self.f_obj

    def __exit__(self, exc_type, exc_val, traceback):
        # Dosya kapama islemi
        self.f_obj.close()


with File('kerem.txt', 'w') as f_write:
    f_write.write('Test...')
    print(f"Dosya Kapatildi mi?[With Body] -> {f_write.closed}")

print(f"Dosya Kapatildi mi?[With blogu disi] -> {f_write.closed}")
Dosya Kapatildi mi?\[With Body\] -> False
Dosya Kapatildi mi?\[With blogu disi\] -> True

Bu kadar örnek yeterli diye düşünüyorum. Şimdi ikinci bir yolumuz olan decorator yapısını kullanarak nasıl contextmanager protokolünü destekleyen kod yazabiliriz ona geçelim.


ContexManager Decorator Kullanımı

  • Diğer yöntem ise python dilinin sunduğu bir kütüphaneyi kullanmak. Bu kütüphanenin adı contextlib. Bu kütüphane içinde ki contextmanager decorator’ünü kullanacağız.

Kısaca decoratör yapısını hatırlayalım…

  • Decorator yapılar parametre olarak fonksiyon alıp geriye fonksiyon döndüren yapılardır. İki tür decorator yapısı var.
  1. class decorators
  2. function decorators
# Kullanımı

@<fonkismi>(<varsa\_parametreler>)
def func():
    pass

Ufak bir hatırlatmadan sonra örneğimize geri dönelim.

#!/usr/bin/env python3

from contextlib import contextmanager

@contextmanager
def dosyaisleme(dosya_adi, mod):
    dosya = open(dosya_adi, mod)  # __init__ islemi
    yield dosya                   # __enter__ islemi
    dosya.close()                 # __exit__ islemi

with dosyaisleme('kerem.txt', 'w') as f_write:
    f_write.write('Test...')
    print(f"Dosya Kapatildi mi?[With Body] -> {f_write.closed}")

print(f"Dosya Kapatildi mi?[With blogu disi] -> {f_write.closed}")
Dosya Kapatildi mi? [With Body] -> False
Dosya Kapatildi mi? [With blogu disi] -> True

  • contextmanager decorator yapısının kodunu incelersek contextlib.py içerisinde ki _GeneratorContextManager isimli sınıfı kullandığını göreceğiz. Sınıf kodunu incelersek tıpkı bizim yazdığımız yapı gibi __init__ __enter__ ve __exit__ metotlarını göreceksiniz.

Yararlanabileceğiniz diğer kaynaklar:

Baris Simsek https://www.youtube.com/watch?v=XcY4jDj4VkE

PythonTips http://book.pythontips.com/en/latest/context_managers.html

Jeff Knupp https://jeffknupp.com/blog/2016/03/07/python-with-context-managers/

Emacs - Pdf Tools
+

Merhaba, son zamanlarda emacs’ı aktif bir şekilde kullanmaya başladım. Yazılım geliştirirken, internet sitelerinde dolaşırken, gelen epostalarımı okurken ve hatta pdf okurken emacs kullanmaya başladım. Böyle olunca elisp dili üzerinde de bilgi sahibi olmaya başladım.

Herneyse konumuz emacs’ı eklentiler ile nasıl daha aktif kullanabiliriz. Kullandığım ve beğendiğim eklentileri tanıtmak istedim. Emacs severler veya denemek isteyenler bu adımları takip edebilir.

Emacs pdf-tools için önce init.el yada .emacs yani konfigürasyon dosyanız hangisi ise gerekli paketi yüklememiz gerekiyor. Ben emacs’ı mac bilgisayar üzerinde kullanıyorum. Bunun için gerekli birkaç program kuracağız. Eğer programları kurmassanız emacs bunları sizin için otomatik yapacaktır.

Kullanacağımız plugin -> https://github.com/politza/pdf-tools

Github içinde kurulum adımları adım adım verilmiş. Emacs üzerinde birden fazla yöntemle kurulum mümkün. Ben use-package kullanacağım. Konfigürasyon dosyamın içine pdf-tools ayarlarını ekliyorum.

;; ----------------------------------------------------
;; --------------------- PDF TOOLS --------------------
;; ----------------------------------------------------
;; brew install pkg-config poppler automake
(use-package pdf-tools
  :ensure **t**
  :config (pdf-tools-install))

Konfigürasyonumuzu yaptıktan sonra kaydedip, emacs reload edelim. Reload etmek için emacs uygulamanızı kapatıp açmanıza gerek yok. M-x load-file ile sizin konfigürasyon dosyanızı vererek yapabilirsiniz. Bu sayede uygulamadan çıkmadan kurulumlar otomatik yapılacaktır. Hatta bu kısımda bir otomatikleştirme yapalım ve bunu bir kısayola atayalım.

Bir fonksiyon yazalım.

;; MyFunc -> Reload Emacs Configuration
(defun my/reload-init-file ()
  (interactive)
  (load-file "~/.emacs.d/init.el"))
(global-set-key (kbd "C-c r") 'my/reload-init-file)

C-c r bizim init.el ayarlarımıza bakarak, eğer bir değişiklik var ise tekrardan kurulum yapacak ve aktif hale getirecektir.

Kurulum adımları şöyle gözükecektir.

Emacs Pdf Tools

Şimdi eklentimizi bir deneyebilim. C-x C-f ile herhangi bir pdf dosyasını açalım.

Open a Pdf File with Emacs

Örnekte gördüğünüz gibi Pdf görüntülebiliyoruz. “O” kısayola basarak index görebiliriz. Yada pdf içinde çok hızlı aramalar yapabiliriz. Bu komutların veya kısayolların hepsini bilmenize gerek yok. Çünkü helm size yardımcı olacaktır. M-x basarak pdf yazdığımızda bize komutları ve yanlarında var ise kısayollarını gösterecektir.

Emacs PDF

Bir sonraki emacs yazımızda görüşmek üzere.

Web Crawler/Spider ve Scrapy
+

Web Crawler Nedir?

  • Crawl kelimesi “emeklemek, yavaş ilerlemek” anlamına gelmektedir. Belirli bir hedefe, amaca ulaşmak için yapılan bir takım işlemlerdir.

Web Crawler


Web Crawler Nasıl Ortaya Çıktı?

  • Web crawler arama motorlarının doğuşu ile beraber ortaya çıktı. Arama motorlarının internet üzerinden linkleri toplayıp, indexleyip, insanların bilgiye doğru ve hızlı bir şekilde ulaşmasını amaçlamışlar. Kısaca linkleri izlemek ve bilgi toplamak amacıyla ortaya çıkıyor.

Web Crawler / Search Engine


Neden Crawler ve Spider isimleri verilmiş?

  • Aslında yukarıda da söylediğim gibi belirli zaman aralıklarıyla sürekli olarak aynı işlemlerin tekrarlanması(linklerin izlenmesi), crawl kelimesinin bebeklerin ve sürüngenlerin hedefe ulaşmak için yaptıkları bir takım işlemlere benzetilmesi, linklerin izlenmesi ve ağ yapısının büyümesinin örümceklerin ağ örmesine benzetilmesidir.

Crawler/Spider


Web Crawler Basit Çalışma Mantığı Nasıldır?

  1. Web sayfalarını indir
  2. Bağlantıları çıkar
  3. Anahtar kelimeleri çıkar
  4. Kelime ve sayfa bilgisini indexer geçir
  5. Bulunduğun bağlantıdan devam et ve aynı adımları tekrarla.

Web Crawler Basit Çalışma Mantığı

Web Crawler Basit Çalışma Mantığı

  • Web Crawler kendi içinde kategorilere ayrılmaktadır. Şimdilik sadece isimlerini vermek ile yetineceğim ve bu kısımları scrapy ile bir uygulama yazarken ihtiyaçlar dahilinde nasıl kullandığını göstereceğim. Bunlar; DeepCrawl, Full Body Text, Meta Robot Tag, Frame Support, Meta Description, Stop Words, Robots.txt, Meta Keywords gibi..

Web Crawler Kavramları Nelerdir?

  • Kavramlar konusu genişletilebilir fakat genel olarak en çok kullanılan 2 konsept var. Focused Crawler ve Distributed Crawler

Web Crawler Kavramları


Web Crawler vs Web Scraping

  • Kısaca web crawler linkleri izlemek, listelemek, indekslemektir. Google, Yandex, Yahoo, Bing gibi arama motorlarının sitelere gönderdikleri örümcekler, botlar gibi…
  • Web Scraping ise kişinin isteğine bağlı olarak belirli bilgilerin toplanması ve işlenmesidir.

Web Crawler vs Web Scraping


Web Crawling/Scraping Legal mi?

  • Web crawling yasal bir olaydır. İllegal olan kısmı dataların toplanması değil, toplandıktan sonra analiz etmek, datayı anlamlandırmak ve kurumlara satılmasıdır. Bunun dışında bilgi toplarken dikkat etmemiz gereken kurallar vardır.

  • Web crawling doğru şekilde yapılmadığı sürece engellenebilirsiniz daha kötüsü karşı tarafı çökertebilirsiniz. Bir çeşit DDOS saldırı yapabilirsiniz. Bununla ilgili olarak şöyle bir resim paylaşacağım. Altına ufak bir açıklama geçeceğim ve konuyu daha fazla uzatmadan Scrapy kullanımını ve mimarisini anlatmaya başlayacağım.

Web Crawling/Scraping Legal mi?


Dikkat Etmemiz Gerekenler

  • Burada ki dikkat etmemiz gereken nokta bilgi toplarken karşıda ki sistemin kaynaklarını kullanıyor olmamız. Tabi birçok site bu konularla ilgili güvenlik önemleri alıyor, hatta robots.txt içinde siteyi crawl ederken uymanız bazı kurallar bırakırlar. Zaman sınırlaması, user-agent bilgisi gibi. Ama aynı zamanda bu konu ile ilgili çok fazla case var.

  • Örneğin aynı ip adresinden belirli bir süre içinde birden fazla linke ulaşmak istemesi, User-Agent’a göre filtreleme yapma gibi…

  • Sisteminizi, crawlerınızı tasarlarken bunları göz önünde bulundurarak, sisteme saldırıyor gibi değil, insancıl zamanlarla beraber(politeness time) siteleri gezmemiz gerekmektedir.


Crawling için kullanabileceğimiz birden fazla kütüphane, framework var. Bunlardan bazılarını saymak gerekirse;

Crawling Frameworks/Libs

  • Şuan için sadece Scrapy framework anlatacağım. Arada Selenium, requests ve beautifulsoup kütüphanelerine de gönderme yaparız.

Scrapy

Python Scrapy


Scrapy Nedir?

  • Scrapy nedir çok kısaca tekrar edecek olursak, web içeriklerini kolaylıkla ve hızlıca tarayabilmemizi sağlayan gelişmiş bir frameworktur diyebiliriz.

  • Şimdi sistemimize scrapy kurarak başlayalım. Python projeleri üzerinde çalışırken virtualenv oluşturmak daha iyi olacaktır. Bu makaleye özgü bir konu değil ondan direk olarak geçiyorum ama bilmiyorsanız ve öğrenmek istiyorsanız, şunları inceleyebilirsiniz. virtualenv/virtualenvwrapper

Scrapy -> https://scrapy.org/

Scrapy Tutorial -> https://docs.scrapy.org/en/latest/


Scrapy Kurulumu

  • Sanal ortamımızı oluşturalım. Scrapy paketimizi python paket yöneticisi ile yükleyelim.

Scrapy Help

  • Eğer yükleme başarılı olduysa scrapy yazdığınızda kullanabileceğiniz komutlar yukarıda ki gibi ekrana gelecektir.
  • Scrapy kurduk ve artık bir proje oluşturup kodumuzu yazmamız gerekiyor. Scrapy çok geniş bir yapıya sahip olduğu için herşeyi burada anlatmamız çok güç olur. Bu yüzden bir tane web sitesi seçelim ve site üzerinde ki linkleri toplayalım.

Planlama Yapalım!!!

  1. Hedef siteyi belirle
  2. Sitenin robots.txt gözden geçir
  3. Scrapy komutları ile projemizi oluştur
  4. Modelin varsa items.py içine tanımla
  5. Spider oluştur
  6. Settings dosyasını düzenle
  7. Çalıştır ve dataları al.
  • Şimdi böyle minimal bir planımız var. Adım adım yapmaya başlıyorum.

1. Hedef siteyi belirle -> https://example.com

2. Sitenin robots.txt gözden geçir -> https://example.com/robots.tx

3. Scrapy projesini oluştur

Scrapy Start Project Scrapy Project Structure

4. Modelimizi items.py içine tanımlayacağız.

  • Modelden kastımız siteyi gezerken, site içerisinden alacağımız verilerin yazılacağı kolonlar gibi düşünebilirsiniz. Biz user isimlerini alalım ve aynı zamanda kaç kişiyi takip ediyorlar, kaç kişi tarafından takip ediliyorlar bilgilerini alalım. Şimdi nasıl olmuş oldu userID, following, followers .. Çıktı olarak json data istiyorum. Tabi siz farklı şekilde çıktılar alabilirsiniz. Database yazabilirsiniz, json, csv olarak alabilirsiniz gibi… Bunun içinde ayrı geliştirme yapmanız gerekiyor.
  • items.py
# -*- coding: utf-8 -*-

# Define here the models for your scraped items
#
# See documentation in:
# https://doc.scrapy.org/en/latest/topics/items.html

import scrapy


class ExampleItem(scrapy.Item):
    # define the fields for your item here like:
    # name = scrapy.Field()
    id = scrapy.Field()
    userID = scrapy.Field()
    followers = scrapy.Field()
    following = scrapy.Field()

5. Spider oluştur.

scrapy genspider SpiderIsmi hedefSite — template=crawl

Create Spider

(scrapyTest) ☁  example  ls example/spiders
__init__.py       __pycache__       example_spider.py
  • Şimdi spiderımız oluşturuldu.

  • spider dizini altında example_spider.py isminde python dosyamızı oluşturdu. Biz bunu kendimiz de oluşturabilirdik. Sadece scrapy komutlarını nasıl kullandığımızı da görmenizi istediğim için yaptım. Biz spider kodumuzu kendimize uygun şekilde yazacağız. Spider yazmadan önce miras alacağınız yapıların dökümanlarına bakmak mantıklı olacaktır. Ekstra bir iş yapmamanız açısından sizin içinde zaman kaybı olmamız olur.

Spiders Doc -> https://doc.scrapy.org/en/latest/topics/spiders.html

Spider kodumuzu yazalım

# -*- coding: utf-8 -*-

from scrapy import Request
from scrapy.spiders import SitemapSpider

from example.items import ExampleItem


class ExampleSpiderSpider(SitemapSpider):
    # Spider ismi -> calistirirken bu ismi kullanacagiz (settings)
    name = 'example-spider'

    allowed_domains = ['example.com']

    # start_urls - Robots.txt kontrol ettikten sonra Sitemap
    start_urls = ['http://example.com/']

    # sitemap icinde belirli url ayiklamak icin rules yazalim
    # /birseyler buldugu linkeri parse fonksiyonuna yollayacak
    sitemap_rules = [("/birseyler", "parse")]


    def parse(self, response):
        # Sitenin yapisina gore parse edelim
        # Clean url seklinde alip parse_detail isimli fonksiyona gonderelim

        if 'bulunamadi' not in response.body:
            # ... filtreler
            # ... xpath / css / selector vs kullanimlari

            for page in pages:
                # ...
                # callback function onemli
                # parse_detail de istenilen datalari parse edecegiz
                yield Request(url=response.url,
                              callback=self.parse_detail)

            # Sayfa paginate mantiginda olabilir
            next_page = response.xpath('//filtreleme')
            if next_page:
                # Bir sonraki sayfayi tekrardan parse kendine gonderecek
                yield Request(response.url, callback=self.parse)
            else:
                return


    def parse_detail(self, response):
        # Modelimizi kullanalim
        item = ExampleItem()
        item['id'] = get_id()
        item['userID'] = get_userID()
        item['followers'] = get_followers()
        item['following'] = get_following()
        yield item
  • Burada bir prototip oluşturduk. SitemapSpider kullandık. Dokümantasyonu okuyarak amaca uygun bir yapıyı kullanmak çok önemli.

  • Biz sitemap.xml dosyasından yola çıkarak sitemap_rules içinde kuralımızı belirttik ve tüm sitemap.xml işleyerek bizim kurallarımıza göre dataları alıp, oluşturduğumuz model yapısını kullanarak dışarıya json, csv data aktarabilir yada direk olarak bir database insert edebiliriz.

  • parse_detail fonksiyonu içerisinde ki get_id() get_userID() gibi fonksiyonlar ExampleSpiderSpider class içerisinde olacaktır. Burada prototip bir uygulama yazdık. Ama pratikte yapacağınızla birebir aynı olacak.

6. Settings dosyasını düzenleyelim

Scrapy Settings

  • Settings dosyasından çok fazla ayar var.

  • Bizim için önemli olanlardan bazıları Concurrent_Requests sayımızı abartmamak. User-Agent kullanmak, Download_Delay 1–3sn arası vermek, Proxy kullanmak gibi…

Settings Doc -> https://doc.scrapy.org/en/latest/topics/settings.html

7. Çalıştır ve dataları al.

scrapy crawl SpiderIsmi -o OutputIsmi.json

Scrapy Run Scrapy Output

Genel olarak yapı böyle. Tabiki de bu sadece SitemapSpider kullanımı için. Dökümantasyonu okuyarak daha fazla bilgi edinebilir, crawler yazabilirsiniz. Eğer takıldığınız, sormak istediğiniz bölümler var ise sorabilirsiniz. Redis, Rethinkdb gibi entegrasyonlar yapacaksanız öncelikle pipeline kısmında bir geliştirme yapmanız gerekecektir. Daha sonra settings kısmını düzenlemeniz yetecektir. Kendiniz proxy yazacaksanız middleware dosyasının içine yazabilirsiniz. Hali hazırda çok fazla middleware yazılmış, siz bir geliştirme yapmadan önce scrapy dökümantasyonlarını okumanızı tavsiye ederim.

C++ References
+
  • C dilinde kullandığımız pointerlar konusu C++ dilinde yerini referanslara bırakıyor. Ama C++ içinde de pointerları kullanabiliyorsunuz. Referanslar programcıya daha kolay bir kullanım sunuyor. Kullanıdığımız referanslar sadece notasyonel bir hile, arka planda aslında pointerlar var.
  • Referans makina ile ilgili bir kavram değil. Makinada yine herşey adresler.
  • & bu bir declarator, adres operatörle karıştırmamak gerekiyor. Çünkü bu tanımda kullanılıyor.
  • Referansların Pointerlardan tek farkı seviyesi yükseltilmiş.
  • İlk değer vermeden referans oluşturmak sentaks hatası
#include <iostream>
int main() {

    int &kerem;
    // Declaration of reference variable 'kerem' requires an initializer

    return 0;
}
#include <iostream>
int main() {

    int x = 10;
    int y = 30;

    int &r = x; // artık r heryerde x'in yerine geçiyor

    r = 45;
    std::cout << "x -> " << x << std::endl;  // x -> 45

    r = y;
    std::cout << "x -> " << x << std::endl; // x -> 30

    return 0;
}
  • Aslında ben r dediğimde *ptr gibi bir yapı kullanıyorum ama bu artık derleyiciyi ilgilendiren kısım. Bizim işimizi tamamen kolaylaştıran bir yapı.
#include <iostream>
int main() {

    int x = 10;

    int &r1 = x;  // r1 x demek
    int &r2 = r1; // r2 de x demek
    // pointer to pointer DEGIL

    r1++;
    r2++;

    std::cout << x << std::endl; // 12

    return 0;
}
  • C++11 ile gelen yeni araçlarla birlikte tür kontrolü çok daha fazla artmıştır.
#include <iostream>
int main() {

    int x = 10;

    int &r = x;  // r demek = x demek
    int *ptr = &r; // ptr'ye x'in adresini atadim
    // pointer to pointer var fakat REFERANS to REFERANS YOK

    *ptr = 99;

    std::cout << "x -> " << x << std::endl; // x -> 99

    r++;
    std::cout << "x -> " << x << std::endl; // x -> 100

    return 0;
}
  • Referansları kullanma amacımız temel olarak call by reference yapmak.
  • C dilinde call by reference yapmanın tek yolu var, adresini fonksiyona göndermek.(fonksiyonun parametresi pointer olması gerekir)
  • C++ dilinde buna ikinci bir yöntem geliyor.
#include <iostream>
void gf(int &r) //argumanlar parametre degiskenlerini initialize eder
{
    r = -1;
}
void func(int *ptr) // c dilinde CALL BY REF yapmak icin pointer gerekiyor
{
    *ptr = 99;
}
void foo(int a)
{
    a = 33;
}
int main() {

    int x = 10;

    foo(x);
    std::cout << "x -> " << x << std::endl; // x -> 10

    func(&x);
    std::cout << "x -> " << x << std::endl; // x -> 99

    gf(x);
    // C de CALL BY VALUE olacakti.(Fonksiyon koduna bile bakmaya gerek yok)
    // C++ de kesin olarak fonksiyon koduna bakilmali. Bu bir yazim kolayligi

    return 0;
}

Soru: C++ dilinde biri *ptr ile yazılmış bir fonksiyon var. Biri de &r parametresiyle yazılmış bir fonksiyon var. Hangisi daha verimli çalışır?

  • Burada pointer ile referans arasında ki ilişki mantık anlaşılmış mı açısından güzel bir soru. Her iki semantik ile birer swap fonksiyonu yazalım.
#include <iostream>

// referans semantigi kullanalim
void swap_r(int &r1, int &r2)
{
    int temp{r1}; // uniform initializer
    r1 = r2;
    r2 = temp;
}

// pointer semantigi kullanalim
void swap_p(int *p1, int *p2)
{
    int temp = *p1;
    *p1 = *p2;
    *p2 = temp;
}

int main() {

    int x = 11;
    int y = 33;

    swap_p(&x, &y);
    std::cout << x << " " << y << std::endl;   // 33 11

    swap_r(x, y);
    std::cout << x << " " << y << std::endl;   // 11 33

    return 0;
}

Cevap: Hiçbir farkı yok, ikisinde de aynı makina kodunu üretir. Kullanabildiğimiz heryerde referans kullanacağız, mecbur olduğumuz birkaç senaryo da pointer kullanacağız.

  • Referanslar kendisi const olan bir pointerın seviyesi yükseltilmiş halidir. Burayı anlamak çok önemli ve scope boyunca referans gösterdiği nesneyi temsil eder, başka bir nesneyi gösteremez. Yani referanslara ilk değer vermek zorunlu derken arka plan da const oldukları içindir.
#include <iostream>
int main() {

    const int x = 10; // const semantigi

    int *ptr = &x;
    // Cannot initialize a variable of type 'int *'
    // with an rvalue of type 'const int *'

    return 0;
}
#include <iostream>
int main() {

    const int x = 10; // const semantigi

    int &r = x; // Binding value of type 'const int' to reference to type
    // 'int' drops 'const' qualifier
    // const T x turunden T x turune donusum YOK

    const int &r = x; // GECERLI

    return 0;
}
#include <iostream>

void func(int &r);  // sabit gonderemeyiz
void foo(const int &r); // okuma yapacagimizi belirttik - const

int main() {

    func(10); // GECERSIZ

    foo(10); // GECERLI

    return 0;
}
  • Böyle bir durumda func() fonksiyonunu sabit ile çağıramayız ancak nesne ile çağırabiliriz.
  • Ama foo() gibi bir fonksiyon gördüğümüzde buna sabitte gönderebiliriz.
void func(const double &r);
// sabit gonderebildigim gibi farkli turde bir nesnede gonderebilirim
// T &r yerine const T &r
  • Burada ki hikaye şöyle const bir referasa sabitle ilk değer verdiğimizde derleyici arka planda geçici bir nesne oluşturuyor.
  • Geçici nesneler bu dilde const kabul ediliyor.

ADRESE GERİ DÖNEN FONKSİYONLAR (C Pointer Semantiğinde)

  • Yapılabilecek en kaba hatalardan biri otomatik ömürlü nesnenin adresi ile geri dönmek, otomatik ömürlü demek bir fonksiyonun çalışmasının bitmesiyle nesnenin bellekten silinmesidir.

Adrese geri dönen fonksiyonlar run time hatası olmadan neye geri dönebilir?

  1. Static bir nesnenin adresi ile geri dönebilirler, programın başından sonuna bellekte duruyor.

    • (static keywordü alan, global değişkenler, stringler) Kim beni çağırırsa çağırsın ona aynı değeri geri dönüyorum demek.
  2. Dinamik ömürlü nesne adresi,

  3. Kendi adresiyle geri dönen (sizden alıyor size veriyor)

int &func(void);
func() = 45; // C dilinde bu olamaz
// C dilinde boyle yapabiliriz
int *func(void);

int main() {

    *func() = 45;

    return 0;
}
// C++ dilinde boyle yapabiliriz
#include <iostream>

int &func(void);

int main() {

    func() = 45;

    return 0;
}
#include <iostream>
int g = 100;
int &func()
{
    return g;
}

int main() {

    std::cout << "g -> " << g << std::endl; // g -> 100

    func() = 999; // g'ye atama yapmis oluruz
    std::cout << "g -> " << g << std::endl; // g -> 999

    ++func();
    std::cout << "g -> " << g << std::endl; // g -> 1000

    int &r = func(); // Ilk deger vermek... r demek artik g demek
    std::cout << "r -> " << r << std::endl;  // r -> 1000

    return 0;
}
  • Yeniden bir hatırlatma pointer to pointer var ama reference to reference yok
#include <iostream>

int main() {

    int x = 10;

    int &r1 = x;
    int &r2 = r1;  // ref to ref degil

    return 0;
}
#include <iostream>

int func();

int main() {

    int &&r = func(); // ref to ref DEGIL
    // C++11 r value reference

    return 0;
}
  • R value reference konusunu ilerde tek makale olarak anlatabiliriz.
  • Move semantiği için sağ taraf değeri uydurulmuş.
#include <iostream>

using namespace std;

ostream &func(ostream &r)
{
    r << "\n ************************* \n";
    return r;
}

int main() {

    int x = 999;

    // cout ve x referans yoluylu func fonksiyonuna gonderiliyor
    func(cout) << x << endl;
    //  *************************
    // 999

    // func fonk'nun geri donus degeri referanssa aldigi nesneyi geri vericektir
    // bu referansa geri donmekle yapilan bir hile

    return 0;
}
  • Elemanları referans olan bir dizi diye bir kavram yok. Ama bir referans dizi yerine geçebilir
int main() {

    int a[5] = {0, 1, 2, 3, 4};
    int(&r)[5] = a;  // r demek = a demek
    // bir diziye referans var
    // elemanlari referans olan DIZI YOK

    for (int k = 0; k < 5; k++)
        r[k]++;
    return 0;
}

POINTERLAR vs REFERANSLAR

Özetleyecek olursak;

  • Bir pointer kendisi const olmak zorunda değil, hayatı devam ettiği sürece farklı nesneleri gösterebilir.
  • Referansta böyle değil kendi const, referans hep aynı nesnenin yerine geçer.
  • Elemanların referans olan bir dizi diye birşey yok ama bir referans bir dizinin yerine geçebilir.
  • Pointer dizileri var.
#include <iostream>

int main() {

    int x = 10;
    int y = 22;
    int *ptr = &x;  // ptr'ye x'in adresi ile ilk deger verdik

    *ptr = 20;  // x = 20
    int *&r = ptr;  // r p ptr'nin yerine gecen bir isim, ptr'ye bir referans al
    r = &y;  // ptr'ye y'nin adresini atamak demek    *r artik y
    ++r;

    std::cout << "x -> " << x << std::endl;  // x -> 20
    std::cout << "y -> " << y << std::endl;  // x -> 22

    return 0;
}
#include <iostream>

int main() {

    int x = 10;
    int *ptr = &x;  // ptr x'i gosteriyor
    int **p = &ptr;  // p ise ptr'yi gosteriyor

    int **&r = p;  // r demek = p demek
    ++**r;  // r'nin yerine gecen x'i arttir

    std::cout << "x -> " << x << std::endl;  // x -> 11

    return 0;
}
  • Referansın Referansı diye bir kavram yok.
  • C ve C++ pointer değişkenler iki konumda olabilir ya bir nesneyi gösterirler yada hiçbir şey göstermiyorlardır.
  • Bir nesneyi gösteriyorlarsa pointerın değeri o nesnenin adresi, hiçbir yeri göstermiyorsa null adresidir.
C++ Copy Constructor
+

Soru cevap olarak gidersek konuyu daha iyi anlayabileceğimizi ve anlatabileceğimi umuyorum.


Türkçe olarak nasıl çevirebiliriz?

  • Yoğun olarak Kopyalan Kurucu İşlev olarak çevirildiğini gördüm. Genelde forumlar ve bloglarda kısaltmasını görmeniz mümkün. Kısaltması CC.
  • Öyle durumlar var ki, bir nesne hayata değerini başka bir nesneden alarak başlıyor. Böylesi durumlarda hayata gelen nesne için çağrılan constructor a COPY CONSTRUCTOR deniliyor.

Aşağıdaki örneği inceleyelim:

  • Bu örnekte C++03 ve C++11 kullanımlarına da örnek verelim.
// Copy Constructor
#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass() {
        cout << "default constructor" << endl;
    }

    Myclass(const Myclass &r)
    {
        cout << "copy constructor" << endl;
    }

};


int main()
{
    Myclass m1;         // default constructor
    Myclass m2{m1};   // C++11 - copy constructor
    Myclass m3 = m1;   // C++03 - copy constructor
    Myclass m4(m1);   // C++03 - copy constructor


    return 0;
}
  • Gördüğünüz gibi m1 oluşturulduktan sonra m2 nesnesini m1 ile oluşturuyorum. Burada copy constructor devreye giriyor.
  • İkinci örneğe baktıktan sonra biraz daha net anlayacağız.
// Copy Constructor

#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass() {
        cout << "default constructor" << endl;
    }

    Myclass(const Myclass &r)
    {
        cout << "***********************************" << endl;
        cout << "copy constructor" << endl;
        cout << "this  = " << this << endl;
        cout << "&r    = " << &r << endl;
        cout << "***********************************" << endl;
    }

};

int main()
{
    Myclass m1;
    Myclass m2{m1};   //C++11

    cout << "&m1  = " << &m1 << endl;
    cout << "&m2  = " << &m2 << endl;



    return 0;
}

/*
default constructor
***********************************
copy constructor
this  = 0x7ffee3f4c450
&r    = 0x7ffee3f4c458
***********************************
&m1  = 0x7ffee3f4c458
&m2  = 0x7ffee3f4c450
*/
  • Burada anlayabilmek için this göstericisi ve adresleri ekrana yazdırdım.

Peki Copy Constructor Yapısı Nasıl?

  • Copy Constructor kullanımını örnekte görmüş olduk. Biraz açıklayalım. Sınıf ismi ile aynı fakat diğer nesneye de ulaşmamız gerekiyor. Nesneye ulaşmam için onu referans yoluyla almam gerekiyor ve nesneyi okuma amacıyla kullanacağım için const olması gerekiyor. Fonksiyonumuzun yapısıda böyle.

Ne zaman Copy Constructor çağırılıyor?

  • Şimdi az önce söylediğimiz gibi bir nesne hayata kendi türünden başka bir nesnenin değerini alarak geldiğinde çağırılıyordu. İşte burada bir soru daha aklımıza geliyor.

Hangi durumlarda Copy Constructor çağırılıyor?

  • 3 tipik senaryo var.
  • Senaryo 1 ~> Açık ilk değer verme, (ilk değer verme çeşitlerini 1. örnekte yazdım)
  • Senaryo 2 -> Bir fonksiyon var, bu sınıfın parametresi bir sınıf türünden. (Dikkat sınıf türünden diyorum, sınıf türünden bir pointer veya referans değil) Bahsettiğim call by value, call by reference değil.

Senaryo 2 için bir örnek verelim.

// Copy Constructor
#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass() {
        cout << "default constructor" << endl;
    }

    Myclass(const Myclass &r)
    {
        cout << "***********************************" << endl;
        cout << "copy constructor" << endl;
        cout << "this = " << this << endl;
        cout << "&r = " << &r << endl;
        cout << "***********************************" << endl;
    }

};


void gfunc(Myclass m)
{
    cout << "gfunc cagrildi" << endl;
    cout << "&m = " << &m << endl;
    cout << "---------------------------------------------" << endl;

}


int main()
{
    Myclass m1;

    cout << "&m1 = " << &m1 << endl;
    gfunc(m1);


    return 0;
}

/*
default constructor
&m1 = 0x7ffeeeeb0468
***********************************
copy constructor
this = 0x7ffeeeeb0460
&r = 0x7ffeeeeb0468
***********************************
gfunc cagrildi
&m = 0x7ffeeeeb0460
---------------------------------------------
*/
  • Senaryo 3 -> Biraz daha zor bir senaryo. Çünkü ortada bir nesne görünmüyor :) Ama makina koduna yada assembl koduna bakılacak olursa orada görülecektir. Görünmemesinin sebebi bu isimlendirilmiş bir nesne değil. Bu durumda hayata gelen nesne fonksiyonun geri dönüş değerini tutacak olan geçici nesnedir. O zaman this burada geçici nesne. Fonksiyonlar bir türe geri döndüğünde (int yada int & demek istemiyorum) çağırılan fonksiyonun, çağıran fonksiyona değer iletmesi için bizim görmediğimiz bir nesne hayata geliyor.

Peki bu nesne değerini nereden alıyor?

  • return expression dan alıyor.

Bu geçici nesneye değer atama olarak mı gidiyor yoksa ilk değer olarak mı veriliyor?

  • ilk değer olarak veriliyor.

Senaryo 3 için bir örnek verelim.

#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass() {
        cout << "default constructor" << endl;
    }

    ~Myclass() {
        cout << "destructor" << endl;
    }

    Myclass(const Myclass &r)
    {
        cout << "***********************************" << endl;
        cout << "copy constructor" << endl;
        cout << "***********************************" << endl;
    }

};


Myclass g;

Myclass gfunc()
{
    return g;
}


int main()
{
    cout << "main basliyor" << endl;
    gfunc();
    cout << "main bitiyor" << endl;

    return 0;
}

/*
default constructor
main basliyor
***********************************
copy constructor
***********************************
destructor
main bitiyor
destructor
*/
  • Burada default const. global nesne için çağırıldı. Global olduğu için main den önce çağırıldı.

Peki neden copy constructor çağırıldı, nereden çıktı?

  • Çünkü ortada herhangi bir nesne yok. Anlatmak istediğim nokta burası. Bu örnekte bir destructor yazdık. Anlatmak istediğim konu biraz daha anlaşılıyor hale geliyor. Yani bu örnekte 2 kez destructor çağırılıyor. 2. destructor sizin görmediğiniz nesnenin..

Copy Constructor biz yazmazsak derleyici bizim için bir CC yazacak mı?

  • Evet compiler bizim için bir CC yazacak.
  • Derleyicimizin yazdığı CC non-static inline public üye fonksiyonudur. Tıpkı constructor ve destructor da olduğu gibi.
  • CC bizim tarafımızdan yazılması gereken durumlar var. Durumu basite indirgeyerek şöyle anlatabilirim. Eğer derleyicinin yazdığı destructor ile yetinemiyorsak yani kaynakların geri iadesi için destructor yazıyorsak kesinlikle copy constructor ı biz yazmalıyız. C++11 öncesinde destructor siz yazarsanız CC yazmamanız bir sentaks hatası değil. C++11 de bunu depracated ilan ettiler.
  • Yani kısaca şöyle açıklayabiliriz, destructor var ise demek ki kaynaklar geri verme işi var. O zaman kaynakları release etme gibi bir tema varsa sınıf nesneleri birbirine kopyalandığında biz pointerları yada referansları kopyalamış oluyoruz. Buda bağımsızlık ilkesini tamamen bozar. Bağımsızlığı kendin elde edeceksin. Nasıl olucak bu iş? CC kendin yazarak.
  • Konuyu açıklayan birkaç örnek yazmaya çalışalım. Şöyle bir senaryo oluşturuyorum.
  • Bir tane int türden öğe olsun, ismin uzunluğunu bulsun(m_len).İsmi dinamik bellek alanında tutalım. Constructor m_len e p pointerinin gösterdiği ismin uzunluğunu hesaplasın, m_p pointerinin m_len+1 karakterlik bir heap te bellek alanını edinsin. Bir de kopyalama işlemi var. Bu örnekte CC derleyiciye bırakıyorum. Derleyici ne görürse karşılıklı birbirine kopyalıyor. Bunlara “memberwise copy” yada “shallow copy” deniyor. (Deep copy bunların tersi) Deep copy ile independency yapacağız.
// Compiler Default Copy Constructor

#include <iostream>
#include <cstring>

using namespace std;

class Name {
    int m_len;
    char *m_p;
public:
    Name(const char *p)
    {
        m_len = strlen(p);
        m_p = (char *)malloc(m_len + 1);
        ////
        strcpy(m_p, p);
    }

    ~Name()
    {
        free(m_p);

    }
    void display()const
    {
        // ismi ekrana yazdiralim
        cout << "(" << m_p << ")" << endl;
    }
    ////
};



int main()
{
    Name x{"Kerem Vatandas"};
    x.display();
    if (1) {
        Name y {x}; // CC cagirildi
        y.display();
        getchar(); // burada scope bitti, y nesnesinin destructor'i cagiralacak
    }
    // kaynak geri verildi ama x nesnesi bunun farkinda degil
    x.display();

    return 0;
}
  • Örnekte gördüğümüz gibi bu senaryoda derleyicinin yazdığı CC işimize yaramıyor. Run time hatası alırız. Şimdi CC kendimiz yazıp bağımsızlık oluşturacağım. Şurayı çok iyi anlamamız gerekiyor, biz bir CC yazarsak derleyici bu koda kesinlikle müdahale etmez. Normal örnekte CC yazma nedenim pointer yani aslında m_len in doğrudan kopyalanmasının benim için bir sakıncası yok. Daha karmaşık bir örnekte olabilir di. 20 tane veri elemanı var diyelim ki, bir tanesi için aslında müdahale etmem gerekiyor. Ama 20 elemandan 19 karşıklıklı kopyalanması biz yapacağız.
// CC Kendimiz Yazalım

#include <iostream>
#include <cstring>

using namespace std;

class Name {
    int m_len;
    char *m_p;
public:
    Name(const char *p)
    {
        m_len = strlen(p);
        m_p = (char *)malloc(m_len + 1);
        ////
        strcpy(m_p, p);
    }

    Name(const Name &r)
    {
        m_len = r.m_len;
        m_p = (char *)malloc(m_len + 1);
        ///
        strcpy(m_p, r.m_p);
    }

    ~Name()
    {
        free(m_p);

    }
    void display()const
    {
        cout << "(" << m_p << ")" << endl;
    }
    ////
};



int main()
{
    Name x{"Kerem Vatandas"};
    x.display();
    if (1) {
        Name y {x};
        y.display();
        getchar();
    }

    x.display();

    return 0;
}
  • CC maliyetli bir yapı. Bir nesneye başka bir nesne ile değer vermemiz. Call by value çağrısı, MyClass sınıfına geri dönen fonksiyon vs bunlar ciddi maliyetler. Senaryoyu biraz abartacak olursak, name olmasında matris olsun(matrislerde dev gibi olsun :D ). Hadi diğer kaynakları bıraktık, heapteki kaynak o kadar büyük olur ki, her CC belki onbinlerce bytelık bellek alanının birbirine kopyalanması anlamına gelir. Ama diğer taraftan da bunu yapmak zorundayım. Çünkü runtime hatası olması daha mı iyi? … Yani CC bazı durumlarda maliyet artabilir. Özel bir senaryo uyduralım, kopyalama yaptığım x nesnesinin hayatının kesinlikle biteceğini biliyorsam ben kopyalama yerine o adresi devralırım. (Move semantiği -> r_value ref.)

Atama Operator Fonksiyonu

// Copy Assignment Operator
#include <iostream>
#include <cstring>

using namespace std;

class Name {
    int m_len;
    char *m_p;
public:
    Name(const char *p)
    {
        m_len = strlen(p);
        m_p = (char *)malloc(m_len + 1);
        ////
        strcpy(m_p, p);
    }

    Name(const Name &r)
    {
        m_len = r.m_len;
        m_p = (char *)malloc(m_len + 1);
        ///
        strcpy(m_p, r.m_p);
    }

    Name &operator=(const Name &r)
    {
        if (this == &r)
            return *this;

        free(m_p);

        m_len = r.m_len;
        m_p = (char *)malloc(m_len + 1);
        ///
        strcpy(m_p, r.m_p);

        return *this;
    }

    ~Name()
    {
        free(m_p);

    }
    void display()const
    {
        cout << "(" << m_p << ")" << endl;
    }
    ////
};


int main()
{
    Name x{"Kerem Vatandas"};

    x = x;


    x.display();


    return 0;
}
// Copy Assignment Operator
#include <iostream>
#include <cstring>

using namespace std;

class Name {
    int m_len;
    char *m_p;
    void releaseResources()
    {
        free(m_p);
    }
    Name &deepCopy(const Name &r)
    {
        m_len = r.m_len;
        m_p = (char *)malloc(m_len + 1);
        ///
        strcpy(m_p, r.m_p);
        return *this;
    }

public:
    Name(const char *p)
    {
        m_len = strlen(p);
        m_p = (char *)malloc(m_len + 1);
        ////
        strcpy(m_p, p);
    }

    Name(const Name &r)
    {
        deepCopy(r);
    }

    Name &operator=(const Name &r)
    {
        if (this == &r)
            return *this;

        releaseResources();

        return deepCopy(r);
    }

    ~Name()
    {
        releaseResources();
    }
    void display()const
    {
        cout << "(" << m_p << ")" << endl;
    }
    ////
};



int main()
{
    Name x{"Kerem Vatandas"};

    x = x;

    x.display();


    return 0;
}

Atama Operator Fonksiyonu ile CC ortak parçası “deep copy”.

  • Destructor ile Atama Operatör Fonksiyonunun ortak noktası her ikisi de kaynakları iade ediyor.
  • Buradan şu ortaya çıkıyor, eğer bir sınıf için destructor yazmışsak CC yazmalıyız aynı zamanda atama operator fonksiyonunu da yazmalıyız.
  • C++ en meşhur terimlerinden biri “Big Three(Büyük üçlü)”.

Neye büyük üçlü deniyor?

  • destructor, copy constructor, copy assignment operator oluşturduğu üçlüye. Birimiz varsak diğerlerimizde olucak diyor :)

Kaynak: Rule of Three

  • Kaynakta yazıyor artık C++11 ile bu arttır. Rule of Five
destructor
copy constructor
move constructor
copy assignment operator
move assignment operator
  • Move semantiğini ve Perfect Forwarding gerçekleştirmek için C++11 standartlarında sağ taraf referansı diye bir araç eklendi. Eskiden referans referanstı. Şimdi önce ki referans dediklerimiz şimdi sol taraf referansı olarak anılmaya başlandı. (Lvalue reference)
  • iki && işareti ile belirtilen referansa rvalue(sağ taraf) referansı deniyor.

Kaynak: Lvalue, Rvalue ..

  • Bir sınıfın constructor parametresi sağ taraf değeri olabilir.
  • Sağ taraf referansı parametreli olan taşıma semantiğini yapıcak.
  • Sol taraf referansı parametreli olan kopyalama semantiğini yapıcak.
// Rvalue & Lvalue

class Name {
    int m_len;
    char *m_p;

public:
    Name(const char *p)
    {
        m_len = strlen(p);
        m_p = (char *)malloc(m_len + 1);
        ////
        strcpy(m_p, p);
    }

    Name(Name &&r)
    {
        m_len = r.m_len;
        mp = r.m_p;
        r.m_p = nullptr;

    }

    Name(const Name &r)
    {
        m_len = r.m_len;
        m_p = (char *)malloc(m_len + 1);
        ///
        strcpy(m_p, r.m_p);
    }

    Name &operator=(Name &&r)
    {
        if (this == &r)
            return *this;
        m_len = r.m_len;
        m_p = r.m_p;
        r.m_p = nullptr;
    }

    Name &operator=(const Name &r)
    {
        if (this == &r)
            return *this;

        free(m_p);

        m_len = r.m_len;
        m_p = (char *)malloc(m_len + 1);
        ///
        strcpy(m_p, r.m_p);

        return *this;
    }
  • Evet burada hangisinin çağırıldığını derleyeci belirleyecek, function overloading kurallarına göre.
  • Konu da ek olarak değindiğimiz move semantiği, rvalue .. gibi konuları ayrı bir başlık altında anlatmaya çalışacağım.
C++ Destructor & RAII & inline & friend
+
  • Destructor türkçe terim olarak sonlandırıcı kullanmak istiyorum.
  • Destructor nesnenin hayatı bitince çağırılan fonksiyondur.

Amaç Nedir?

  • Kullanılan kaynakların geri iade edilmesi.
  • Her sınıf için olmasada, bir nesne hayata geldiğinde iş yapabilmek için kaynak edinirler. Bu kaynaklar bir bellek alanı olabilir, bir port kullanılıyor olabilir vs. Ama nesnenin hayatı bittiğinde kaynakların geri verilmesi gerekiyor. (kaynaklardan biri heap ama başka kaynaklarda var tabi) kaynak olarak RESOURCE terimini kullanıcaz.
  • Her sınıfının bir destructor‘ının bulunması zorunlu (dilin kuralı)

Eğer sınıf bir kaynak edinmiyor ise destructor olacak mı?

  • Evet, destructor boş olabilir ama kesinlikle bulunmak zorundadır.
  • Eğer sınıfımızın geri vereceği bir kaynak yok ise derleyicide bizim için boş bir destructor yazabilir.

RAII(Resource Acquisition is Initialization)

  • Default kaynak edinme ilk değer verme yoluyla oluyor.

İsimler sınıf ismi ile aynı ise constructor ile destructor nasıl anlaşılacak?

  • Constructor ile karışmaması için destructor önüne ~ karakteri var
  • Nesnenin hayatı bittiği zaman çağırılır.

Destructor için neden ~ karakteri seçilmiş?

  • Tümleyen anlamında uydurulduğu söyleniyor, bir sınıfın constructor varsa ona tümleyen destructor vardır mantığında… Matematiksel notasyon olarak “not” gibi de düşünülebilir.
#include <iostream>

using namespace std;

class Myclass {
    ~Myclass(); //destructor private olabilir
public:
};

int main()
{
    Myclass m;  //destructor derleyici tarafindan cagrildiginda
//derleyici private oldugundan hata verir


    return 0;
}

Bir nesnenin hayatı ne zaman biter?

  • Bu konu nesnenin ömür kategorisine bağlıdır.
  • otomatik ömürlü nesneler scope sonlarına gelindiği zaman
  • static ömürlü nesneler için main bittiği zaman
  • dinamik ömürlü nesneler, delete operatörünün kullanılmasıyla
  • Destructor geri dönüş değeri yoktur
  • Destructor const olamaz
  • Eğer destructor yazmassam derleyici non-static, inline, public bir destructor yazar
  • Destructor overload edilemez
  • Default destructor diye birşey yok
  • Parametresiz olmak zorunda
  • Private olabilir ama private destructor client tarafından çağrılması sentaks hatası
  • Otomatik ömürlü nesneyi destructor etmek diye birşey yok
  • C++ ile C# ve Java dilleri arasında ki önemli fark, dile gömülü garbage collector yapısı olmaması
  • Garbage collector önemli bir run time maliyeti var
  • Constructor ismi ile çağırılması her zaman sentaks hatası iken destructorin ismi ile çağırılması hata değil. Yani destructor ismi ile çağırılabilir.
  • Ancak bir sınıfın destructor ismi ile çağırılması gereken tek bir senaryo var -> placement new operator
  • Placement new operator başka bir makale de anlatmaya çalışacağım. Şimdilik bir örnek
// Placement New Operator

#include <iostream>

using namespace std;

class Myclass {
    int a, b, c, d;
public:
    Myclass();
    ~Myclass();
};

int main()
{
    unsigned char str[sizeof(Myclass)];

    Myclass *ptr = new (str)Myclass;  //burada kullanilan operator : placement new

    ptr->~Myclass();  //goruldugu gibi destructor cagrisi explicit olarak yapiliyor

    return 0;
}
// Constructor & Destructor

#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass();
    ~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
}

Myclass::~Myclass()
{
    cout << "destructor" << endl;

}

void func()
{
    Myclass m;
}

int main()
{
    int n = 5;

    while (n--)
        func();

    return 0;
}
// Constructor & Destructor


#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass();
    ~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
}

Myclass::~Myclass()
{
    cout << "destructor" << endl;

}

//void func()
//{
//    Myclass m;
//}

Myclass g;

int main()
{
    cout << "main basliyor" << endl;

    cout << "main bitiyor" << endl;

    return 0;
}
// Static Keyword

#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass();
    ~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
}

Myclass::~Myclass()
{
    cout << "destructor" << endl;

}

void func()
{
    static Myclass m;
}


int main()
{
    cout << "main basliyor" << endl;
    int n = 5;

    while (n--) {
        func();
        cout << "*******************" << endl;
    }

    cout << "main bitiyor" << endl;

    return 0;
}
// Destructor Cagirilmiyor

#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass();
    ~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
}

Myclass::~Myclass()
{
    cout << "destructor" << endl;

}


int main()
{
    cout << "main basliyor" << endl;
    Myclass *p = new Myclass;

    cout << "main bitiyor" << endl;

    return 0;
}
// delete keyword

#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass();
    ~Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
}

Myclass::~Myclass()
{
    cout << "destructor" << endl;

}


int main()
{
    cout << "main basliyor" << endl;
    Myclass *p = new Myclass;
    delete p;

    cout << "main bitiyor" << endl;

    return 0;
}
#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
    cout << "this   =  " << this << endl;

    cout << "***************************" << endl;
}


int main()
{
    Myclass *p = new Myclass;
    cout << "p   = " << (void *)p << endl;

    return 0;
}
// Her nesne icin constructor cagirilacaktir

#include <iostream>

using namespace std;

class Myclass {
    int a[4];
public:
    Myclass();
};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
    cout << "this   =  " << this << endl;

    cout << "***************************" << endl;
}


int main()
{
    int n = 10;

    Myclass *p = new Myclass[n];

    return 0;
}
// this

#include <iostream>

using namespace std;

class Myclass {
    int a[4];
public:
    Myclass();
    ~Myclass();

};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
    cout << "this   =  " << this << endl;
    cout << "***************************" << endl;
}

Myclass::~Myclass()
{
    cout << "destructor" << endl;
    cout << "this   =  " << this << endl;
    cout << "***************************" << endl;
}


int main()
{
    cout << "main basliyor" << endl;
    Myclass x;

    cout << "&x    = " << &x << endl;

    cout << "main bitiyor" << endl;

    return 0;
}
#include <iostream>

using namespace std;

class Myclass {
    int a[4];
public:
    Myclass();
    ~Myclass();

};

///sozde cpp dosyasi

Myclass::Myclass()
{
    cout << "constructor" << endl;
    cout << "this   =  " << this << endl;
    cout << "***************************" << endl;
}

Myclass::~Myclass()
{
    cout << "destructor" << endl;
    cout << "this   =  " << this << endl;
    cout << "***************************" << endl;
}

Myclass x;


int main()
{
    cout << "main basliyor" << endl;

    cout << "&x    = " << &x << endl;

    cout << "main bitiyor" << endl;

    return 0;
}

Örneklerden sonra tekrardan hatırlatmalar yapalım.

  • Constructor this göstericine sahip, this ile kullanılabilir.
  • Sınıfın özel fonksiyonları constructor destructor
  • Constructor nesneyi hayata hazırlıyor, bellekte bir yere sahip olması bir storage olması ona bir nesne yapmaz. Bu nesnenin görevini yapabilmesi için bazı işlemleri gerçekleştirmesi gerekiyor. Yani bu nesnelere ilk değer vermekten başka birşey değil.
  • Nesne görevini yapabilmesi için kaynak edinmesi gerekiyor. Bu kaynakları nesneye bağlayan yapı constructor.
  • Dinamik ömürlüler için delete kullanmalıyız, delete kullanmazsak destructor çağırılmaz

inline bir keyword. Fonksiyon için 2 model var

  • Derleyicinin yazdığı destructor non-static inline public demiştik. Biraz bu inline konusundan bahsedelim.
  1. func(); external referans konusu. Derleyiciye bu fonksiyon için çağrı yapılırsa linker aradan çıkar, sen derle, kodu çağıran yere yolla
  2. Derleyici mümkünse bu fonksiyon inline ile aç diyebiliriz. Bunun dezavantajı, implemantasyon ile interface i birbirinden ayırmamış oluyoruz. Burada interfacete implemantasyon da var. Yani derleyicinin bu kodu görmesi lazım. Fonksiyonun kodu değişmemesi gerekiyor, çünkü değişirse cpp dosyalarıda tekrar recompile edilecek.
  • inline en çok constructor ve destructor oluyor. Amaç linkeri işin içine katmamak.
  • Kodu büyük olanların teknik olarak inline edilmesi zor.
  • C++ da bir sınıfın üye fonksiyonlarının sınıfın içinde tanımlanması aslında onları inline yapılmasıdır. inline ricasında bulunulması demek.
  • Global bir üye fonksiyonları inline yapabiliriz.
  • Yerel bir üye fonksiyonunu inline yapabiliriz, tabi her ikisi içinde de tanımlama başlık dosyası içinde yapılacak
//example.h
inline int getmax(int a, int b);
inline int getmax(int a, int b)
{
 return a > b ? a : b;
}
  • Başlık dosyasında hem fonksiyon bildirimi hemde fonksiyon tanımı var.
  • inline keywordu için tercih edilen yer, fonksiyon geri dönüş türünden önce tanımda yer alması daha sık karşılaşılır. Hem bildirim hem tanım varsa inline keywordu tanımda yer alması
  • inline bir implementasyon olarak görün, öyle düşünün
  • Bir fonksiyonu inline yaparak performans elde ederiz anlamına gelmez. Böyle bir beklenti de olabiliriz ama böyle birşey yok. Verimi etkileyen çok faktör var.
  • Kodu küçük, sıkça çağırılabilir mantığında bir performans artışı bekleyebilirsiniz ama verim artmışmı artmamışmı onu profiler ile bakıcaz.
  • Fonksiyon sınıf içinde bildirimini yap, sınıf dışında kodunu yaz
inline void Data::set(int a)
{
 ///////// yazımı daha çok tercih edilir
}

Hangi fonksiyonlar sınıf içinde inline tanımlanabilir?

  • Sınıfın hem static hem non-static fonksiyonları inline tanımlanabilir
static void func(){} ~> sınıf içinde
static olanlar class ile ilgili işlem yapar,
non-static olanlar nesne ile ilgili işlemler yapar,
  • inline hakkı global sınıflara özel bir yerkilendirme ile sınıfın private alanına erişim hakkı verebiliriz, (friend bildirimi)
class Kerem
{
 friend int func(); // global isimli fonk bnm arkadaşım private erişim hakkı
}
// friend keyword kullanimi

#include <iostream>
#include <string>
#include <cstring>

using namespace std;

class Myclass {
    int mx;
public:
    friend void func();
};


void func()
{
    Myclass m;

    m.mx = 10; //gecerli cunku func firend islev
}


int main()
{
    return 0;
}

inline tanım yeri neresidir?

  • cpp dosyası değil. header dosyasında olması gerek
  • ODR (One defination Role) bir kez tanımlama kuralı
  • Tanım atom atom aynı ise problem yok, ama uygulama pratiğinde yeri asla cpp değil
// Name Lookup
    class Myclass {

    public:
        void set(int x)
        {
            mx = x;  //isim arama kurallari degismez
        }
    private:
        int mx; // isim arama, derleyici mx arar, blockta bulamaz sonra class scope’a bakar
    };

C deki struct ile C++ struct aynı değil. struct lar cpp de sınıf

  • Struct larda default bölüm public
  • Struct ında private bölümü olabilir ama azınlık senaryosu
  • Tüm üye fonksiyionlar ve değişkenleri public olan bir sınıfı struct olarak tanımlayabiliriz

class x{public:….}; yada struct x{};

struct Data
{
    int a, b, c;
};

int main()
{
    Data myData = {1, 3, 7};  // member whise initialize
    Data myData{1, 3, 7};     // C++11 ile gelen
}

C++11

Data myData{}; //default initialize
Data myData{1, 3}; // member whise initialize
Data myData(); // geri donus deger Data turunden myData fonksiyon Data myData; // veri elemanlari 0a cekilmez cop deger olur
Data x{}; // 0, 0, 0 ~> 0 primitif ogeler 0 degerine cekilir
Data y; // 3, 1452, 0 ~> cop deger, primitif ogeler cop degeler
#include <iostream>

using namespace std;



struct Data {
    int a, b, c;
    void display()const
    {
        cout << a << " " << b << " " << c << endl;
    }
};

Data g;

int main()
{
    Data x{}; //primitive ogeler 0 degerine cekilir.
    Data y; //primitive ogeler cop degerde.

    x.display();
    y.display();
    g.display();

    return 0;
}

/*
0 0 0
0 0 0
0 0 0
*/

Neden böyle birşey var? Niye böyle birşey yapmışlar?

  • Primitif öğeleri bu olmasada sıfır a çekebilirdi. Ama bunu bize bırakmışlar çünkü primitif üyeler bir dizide olabilirdi.
//C dilinde olmayan dongu deyimi
#include <iostream>

using namespace std;


struct Data
{
int a[1000];

void display() const
{
for(int x: a) // range base for loop
cout << x << " ";
}
};

int main()
{
Data x{};
x.display();
// hepsi 0 ama bunun bir maliyeti var.
// Data x cop deger ile kalacak, dizinin tum ogeleri 0 tasimaktan kurtulduk
}
#include <iostream>

using namespace std;


struct Data {
    int x, y, z;
};


int main()
{
    // oto omurlu
    Data d1{1, 2, 3};
    Data d2{};
    Data d3;

    // dinamik omurlu
    Data *p1 = new Data{1, 4, 5};
    Data *p2 = new Data{};
    Data *p3 = new Data;

    return 0;
}
C++ Constructor
+

Construct -> inşa etmek, kurmak

Constructor -> inşa eden, kurucu

  • Constructor hakkında bilmemiz gereken bazı bilgiler: (Yazının devamında bunları inceleyeceğiz)
  1. Constructor nesneyi hayata hazırlar.
  2. Her sınıfın constructorı mutlaka var.
  3. Ben constructor yazmassam benim yerime derleyici yazıyor.
  4. Constructor sınıf ismi ile aynı olmalıdır.
  5. Constructor geri dönüş kavramı yoktur. (void değil karıştırılmamalı)
  6. Constructor NON-STATIC üye fonksiyonudur.
  7. Constructor const OLAMAZ.
  8. Constructor OVERLOAD edilebilir.
  • Bir sınıf nesnesi için bir bellek alanı elde etmemiz onu kullanılabilir bir sınıf yapmıyor. Kullanılabilir olması için ona bir ilk değer vermek gerekiyor.

  • Bir nesne hayata geldiğinde o nesnenin kullanılabilmesi için bazı kaynaklar edinmesi gerekiyor. Yani sadece ilk değer vermek olarak düşünmeyin, bazıları kaynak edinecekler.

  • C++ dilinde sınıflar için bazı fonksiyonlar var ki mutlaka olmak zorunda. Bu fonksiyonlara özel fonksiyonlar diyoruz. Yani bu fonksiyonlar biz yazarsak bizimkiler kullanılacak, biz yazmassak derleyeci bizim yerimize yazıp kullanacak. Constructor da özel fonksiyonlardan birisi.

  • Constructor için keyfi isim veremiyoruz. Yani bu isim class ismi ile aynı olmak zorunda. Derleyici constructor olup olmadığını sınıf ismi ile aynı olup olmadığına bakarak anlıyor.

  • Constructor’ın geri dönüş değeri gibi bir kavram yok. Yani void değil. Karıştırılmasın geri dönüş değeri diye bir kavram yok.


Constructor static üye fonksiyon mu yoksa non-static üye fonksiyon mu?

  • Constructor non-static üye fonksiyonudur. Çünkü nesnemize ilk değer vereceğine göre büyük olasıklıkla veri elemanlarına değerler yerleştirecek. Veri elemanlarına değer yerleştirmesi için nesnemizin adresini alması lazım. Nesnemizin adresini alması demek this göstericisinin olması demek.

Constructor sınıfın public bölümünde olmak zorunda mı?

  • Hayır böyle bir zorunluluk yok.

  • Eğer sınıfın private bölümüne koyarsak, derleyici tarafından çağırıldığı zaman yine sentaks hatası olur. Hatırlarsak clientlar sınıfın sadece public bölümüne erişebiliyorlardı. Hata olan constructor’ın private olması değil, private bir fonksiyonun çağırılması.

  • Constructor’ı private bölümüne yazacağımız özel bazı senaryolar var. (Örnek: Singleton Pattern)

#include <iostream>

// Private Constructor
class Myclass {
  Myclass();
public:
  void func();
};


int main()
{
  Myclass m; //gecersiz cunku constructor private
  // error: calling a private constructor of class 'Myclass'

  return 0;
}

Default Constructor (Varsayılan Kurucu İşlev)

  • Parametre değişkeni yoksa yada tüm parametre değişkenleri varsayılan argüman alıyorsa bu default constructor

  • Argüman göndermeden çağırılabilen constructorlara default constructor diyoruz


Argüman Nedir?

  • Fonksiyon çağrılarıyla fonksiyonlara gönderilen ifadelere arguman yada actual parameter denir.

  • Biz hiçbir constructor yazmassak derleyici bize non-static inline public bir constructor yazar.


non-static ne demek?

  • static keywordu olmayan, yani this göstericisine sahip olan demek

  • Derleyicinin yazdığı constructor ın static olma imkanı yok, private olma imkanı yok


inline, fonksiyonlar için 2 farklı model var.

  1. Biz bir fonksiyona çağrısı yapıyoruz. Derleyici bu fonksiyona giriş çıkış kodlarını üretiyor fakat bu fonksiyonun kendi kodunun bu koda bağlanması işini linker’a havale ediyor. Peki bunu nasıl yapıyor? external reference yazıyor.

  2. inline fonksiyon dediğimiz şöyle, derleyici fonksiyonun tanımını da görücek, fonskiyonun tanımını gördüğü için fonksiyonu derleyecek object kodunu doğrudan çağırdığı yere gömücek. Yani burada artık linker bypass edilmiş oluyor. inline yapmakla derleyiciye şunu söylüyoruz, derleyici sen linker’i felan bırak gel bu işi aramızda halledelim.

  • inline fonksiyon ile inlined fonksiyonu birbirine karıştırmamak gerek. Bir fonksiyona inline demek böyle bir rica da bulunmak demek. Derleyici bu ricayı yerine getirebiliyorsa artık o fonksiyon inlined olucak.

  • Ama semantik olarak şöyle bir avantaj olacak. Derleyici bunu yerine getirse de getirmese de client tarafından kodla bunun bir alakası yok. Nasıl fonksiyon çağırıyorsak öyle çağırıcaz.

  • Constructor const olamaz

  • Constructor overload edilebilir, fazlaca karşılaşılan bir senaryo

// Constructor Overload

#include <string>
#include <cstring>


class Date {
public:
    Date();
    Date(int d, int m, int y);
    Date(const std::string &); // tarih formatini bir yazi seklinde iletiyoruz
};
//Constructor Overload

#include <string>
#include <cstring>


class Triangle {

public:
    Triangle(double d1, double d2, double d3);
    Triangle(double base, double height);
};

Constructor explicit olarak çağrılamaz. (constructor ismi ile çağırılamaz)

// Constructor Hatalı Çağırma

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass(){}
    Myclass(int, int);
};


int main()
{
    Myclass m;

    m.M(); // gecersiz
    // error: cannot refer to type member 'Myclass' in 'Myclass' with '.'

    return 0;
}
//Constructor C++11 Default Keyword
#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass(int);
};


int main()
{
    Myclass m; // hata boyle bir constructor yok
    // no matching constructor for initialization of 'MyClass'

    return 0;
}

// Cozum -> C++11 ile gelen default keywordu
// Myclass(){}; yada Myclass = default(); // c++11

Function overload kuralları burada tamamen geçerli

// Function Overload
Myclass(long);
Myclass(float);
//main….
Myclass m(10); //embiguity
// Constructor C++11

#include <string>
#include <cstring>


class Myclass {

public:
    Myclass(int);
    Myclass();


};


int main()
{
    Myclass m1;  //default constructor
    Myclass m2{};  //default constructor  - C++11
    Myclass m3();  //function declaration
    Myclass m4 = Myclass();
    Myclass m5 = Myclass{};

    return 0;
}

Nesne ne zaman hayata başlarsa constructor o zaman çağırılıyor.

  • C++ diğer dillerden farklılığı, primitive nesneler gibi, yerel nesneler oluşturabiliyoruz. Yani otomatik ömürlü, static ömürlü ve dinamik ömürlü nesne oluşturabiliyoruz.
// Constructor
#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}

void func()
{
    Myclass m; // stack yer alan, default contructor cagirilacak
}

int main()
{
    for (int k = 0; k < 10; k++)
        func();

    return 0;
}
/*
Myclass default constructor
Myclass default constructor
Myclass default constructor
Myclass default constructor
….
*/
// Default Constructor

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}

void func()
{
    Myclass m1, m2; // hem m1 hemde m2 icin cagirilacak ayri ayri
    cout << "************************" << endl;
}

int main()
{
    for (int k = 0; k < 10; k++)
        func();

    return 0;
}

/*
Myclass default constructor
Myclass default constructor
"************************"
Myclass default constructor
Myclass default constructor
*/

Referans demek nesne demek değil

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}


int main()
{
    Myclass m1; // 1 kere constructor cagirilir
    // Myclass default constructor
    Myclass &r1 = m1; // referans demek nesne demek degil
    Myclass &r2 = m1; // referans bir nesnenin yerine gecen isim

    return 0;
}

Pointer — Constructor

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}


int main()
{
    Myclass *ptr; // constructor cagrisi yok, bu bir pointer, bir instance
    // olusturulmadigi surece constructorin cagrisina neden olmaz

    return 0;
}
// Constructor

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}


int main()
{
    Myclass a[10]; // 10 kere constructor cagirilacak, her elemani icin

    return 0;
}

Static ömürlü nesneler main’den önce çağırılır

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}

Myclass g; // global bir nesne, ömrü static
// static omurlu nesneler main den önce cagirilirlar

int main()
{
    cout << "main basliyor" << endl;

    return 0;
}

Static Yerel Sınıf Nesneleri

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}

void func()
{
    static Myclass m;  //statik yerel sinif nesneleri icin
    //eger bu fonksiyon cagrilmazsa m hayata gelmez
}

int main()
{

    return 0;
}
#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}

void func()
{
    static Myclass m;
}

int main()
{
    cout << "main basliyor" << endl;
    cout << "func cagriliyor " << endl;
    func(); // constructor cagirilacak
    cout << "main devam ediyor" << endl;
    cout << "func bir kez daha cagriliyor " << endl;
    func(); // contructor yazisi cikmaz, artik 2. kez cagirdigimizda hayata gelmiyor
    // static nesneler hayata 1 kere gelir
    cout << "func bir kez daha cagriliyor " << endl;
    func();

    return 0;
}

New Operatörü

  • Dinamik nesne yaratılması için heap te bir alan yaratılması gerekiyor. Bu alanı tahsis eden operatör new (bu bir fonksiyonun ismi) (new bir operatör, operator new fonksiyon)

  • new operatörü önce operator new çağırıyor. Heap te bir alan elde ediliyor. O fonksiyon malloc un eşdeğeri bir fonksiyon. Yani size of myclass kadar yer ayırıyor. O adresi void olarak döndürüyor. Derleyici void myclass* dönüştürerek o adres için constructor çağırıyor.

  • Constructor çağırılmasını sağlayan pointer değil. new operatörünün kullanılması.

// New Operator
#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}

int main()
{
    cout << "main basliyor" << endl;

    Myclass *ptr = new Myclass;
    // main basliyor
    // MyClass default constructor

    return 0;
}

2 tane new operatörü var. tekil new operatörü && [] new operatörü

// New Operator
#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
}

int main()
{
    cout << "main basliyor" << endl;

    Myclass *ptr = new Myclass[5];
    // 5 tane MyClass nesnesi hayata gelir
    // Constructor 5 kere cagirilir

    ////

    return 0;
}

Non-Static Function

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
    cout << "this    = " << this << endl;
}

int main()
{
    Myclass m1;

    cout << "&m1     = " << &m1 << endl;

    return 0;
}
// Uniform Initializer

#include <iostream>

using namespace std;

class Myclass {

public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
    cout << "this    = " << this << endl;
}

int main()
{
    Myclass *ptr{new Myclass};

    cout << "ptr     = " << ptr << endl;

    return 0;
}
// Pointer Aritmetiği
#include <iostream>

using namespace std;

class Myclass {
    int a[4];
public:
    Myclass();
};

/////
Myclass::Myclass()
{
    cout << "Myclass default constructor" << endl;
    cout << "this    = " << this << endl;
}

int main()
{
    Myclass x[4];

    return 0;
}

C++03 ve C++11 Kullanımları

#include <iostream>

using namespace std;

class Myclass {
public:
    Myclass(int);
};

/////
int main()
{
    Myclass *p1 = new Myclass(12); //C++03
    Myclass *p2 = new Myclass{45}; //C++11



    return 0;
}