Python Diliyle Programlamaya Aşina Olanlar İçin C Diline Dair Bilinmesi Gerekenler

Herkese merhaba,

Bu toplulukta programlama konusunda benden daha tecrübeli arkadaşlar var. Onlara selamlar ve saygılarımı sunarım öncelikle. Konuya girmeden önce, kısa bir giriş yapmama müsaade edin.

Benim programlamaya merakım otuz yaşından sonra ortaya çıktı ve şu an otuz yedi yaşındayım. Yani, anlayacağınız gibi yaklaşık yedi senedir program nedir, programlama nedir, algoritma nedir gibi sorulara, daha çok Python dilini kullanarak kendimce cevaplar bulmaya çalışıyorum. C dili ile bu yedi sene içerisinde elbette karşılaştım. Ancak hali hazırda Python’ı keşfetmeye devam ettiğim için ve sıklıkla kullanmak da alışkanlığa yol açtığı için sürekli Python kullanmaya devam ettim.

Şimdi size bu başlık altında 0’dan C anlatacak değilim. Böyle bir iddiam yok. Benim bu başlıkta yapmayı hedeflediğim şey, hali hazırda Python bilen ama C dilini hiç kullanmamış kişilere, C’nin Python diliyle olan meşhur farklılıklarını biraz olsun tanıtabilmek ve bu iki dili birlikte nasıl kullanabileceğimizi göstermek.

Özetle bu başlık, dünya için yeni olmasa da, bazı arkadaşlar için yeni olacak bazı kavramları tanıtmayı hedefliyor.

Şimdi, özel olarak seçtiğim bir örnek üzerinden Python ve C arasındaki farklara odaklanmaya çalışalım:

Python’daki itertools.combinations sınıfının işlevini biliyorsunuzdur. Bilmeyenler için basit bir kullanım ile ne yaptığını gösterelim:

import itertools
print(*itertools.combinations([1, 2, 3, 4], 2))

Yukardaki kodun üreteceği çıktıdan anlaşılacağı üzere, itertools.combinations sınıfı, herhangi bir kümenin n elemanlı alt kümelerini bulur. Peki bu alt kümeleri bulan sınıf için nasıl bir algoritma izliyor? Bunun cevabını Python dokümanını inceleyerek bulabiliriz. Ancak gelin, birlikte başka bir combinations algoritmasını inceleyelim.

def combinations(arr, r):
    if not r:
        yield ()
        return
    for i in range(len(arr)):
        for comb in combinations(arr[i + 1:], r - 1):
            yield arr[i], *comb


print(*combinations(arr, 2))

Şimdi, yukardaki algoritmayı adım adım anlamaya çalışalım:

Her bir öz-yineleme adımının hangi scope'da gerçekleştiğini ve o scope’da yer alan değişkenlerin değerlerinin ne olduklarını, hepinizin çok iyi bildiği girintilerle gösterelim:

combinations fonksiyonunu arr ve r argümanlarıyla çağırdık diyelim:

Fonksiyonumuzun yapacağı işlemleri adım adım açmaya çalışalım:

arr = [1, 2, 3, 4]
r = 2
if not r:
    yield ()
    return
for i in range(len(arr)):

    # i = 0 için:

    for comb in combination(arr[i + 1:], r - 1):
        arr = [2, 3, 4]
        r = 1
        if not r:
            yield ()
            return
        for i in range(len(arr)):

        # i = 0 için:

            for comb in combination(arr[i + 1:]):
                arr = [3, 4]
                r = 0
                if not r:
                    yield ()
                    return
                #  Buradan () üretilir.
                #  Daha ileri gidilmez.
                yield (arr[i], *comb)
                # Yukardaki ifadeyi açalım:
                # yield (2, *())

        yield (arr[i], *comb)
        # yield (1, *(2, *())
        yield (1, 2)  # Ekrana yazdırılır.

        # i = 1 için:

            for comb in combination(arr[i + 1:]):
                arr = [4]
                r = 0
                if not r:
                    yield ()
                    return
                #  Buradan () üretilir.
                #  Daha ileri gidilmez.
                yield (arr[i], *comb)
                # Yukardaki ifadeyi açalım:
                # yield (3, *())

        yield (arr[i], *comb)
        # yield (1, *(3, *())
        yield (1, 3)  # Ekrana yazdırılır.

        # i = 2 için:

            for comb in combination(arr[i + 1:]):
                arr = []
                r = 0
                if not r:
                    yield ()
                    return
                #  Buradan () üretilir.
                #  Daha ileri gidilmez.
                yield (arr[i], *comb)
                # Yukardaki ifadeyi açalım:
                # yield (4, *())

        yield (arr[i], *comb)
        # yield (1, *(4, *())
        yield (1, 4)  # Ekrana yazdırılır.

    # i = 1 için:

    for comb in combination(arr[i + 1:], r - 1):
        arr = [3, 4]
        r = 1
        if not r:
            yield ()
            return
        for i in range(len(arr)):

        # i = 0 için:

            for comb in combination(arr[i + 1:]):
                arr = [4]
                r = 0
                if not r:
                    yield ()
                    return
                #  Buradan () üretilir.
                #  Daha ileri gidilmez.
                yield (arr[i], *comb)
                # Yukardaki ifadeyi açalım:
                # yield (3, *())

        yield (arr[i], *comb)
        # yield (2, *(3, *())
        yield (2, 3)  # Ekrana yazdırılır.

        # i = 1 için:

            for comb in combination(arr[i + 1:]):
                arr = []
                r = 0
                if not r:
                    yield ()
                    return
                #  Buradan () üretilir.
                #  Daha ileri gidilmez.
                yield (arr[i], *comb)
                # Yukardaki ifadeyi açalım:
                # yield (4, *())

        yield (arr[i], *comb)
        # yield (2, *(4, *())
        yield (2, 4)  # Ekrana yazdırılır.

    # i = 2 için:

    for comb in combination(arr[i + 1:], r - 1):
        arr = [4]
        r = 1
        if not r:
            yield ()
            return
        for i in range(len(arr)):

        # i = 0 için:

            for comb in combination(arr[i + 1:]):
                arr = []
                r = 0
                if not r:
                    yield ()
                    return
                #  Buradan () üretilir.
                #  Daha ileri gidilmez.
                yield (arr[i], *comb)
                # Yukardaki ifadeyi açalım:
                # yield (4, *())

        yield (arr[i], *comb)
        # yield (3, *(4, *())
        yield (3, 4)  # Ekrana yazdırılır.

    # i = 3 için:
    for comb in combination(arr[i + 1:]):
    # arr[i + 1:] boş bir liste olduğu için işlem yapılmaz.

Yukarda, combinations fonksiyonunun arr = [1, 2, 3, 4] ve r = 2 için hangi adımları izlediğini, Pythonic bir pseudocode üzerinden anlatmaya çalıştım. Umarım yeterince açıktır. Benzer bir öz-yineleme yöntemini kullanarak istersek itertools.permutations, istersek de itertools.product sınıflarının yaptığı işlemleri yapan fonksiyonları oluşturabiliriz. Ama bu başlıkta bunları anlatmayacağım.

Şimdi, umuyorum ki, şu ana kadar anlatılan şeyler, recursion mantığının Python dilinde nasıl işlediğini açıkça ortaya koyan bir örnektir. C dili Python dili arasındaki karşılaştırmayı daha basit bir örnek üzerinden de gösterebilirdim tabi. Ama dizilerle işlem yapan bir örnek yapıp, C dili ile Python dili arasındaki bir kaç önemli farklılığı vurgulamak için, böyle bir örnek seçmek geldi aklıma. Hem böylece, C’de dizi oluşturmak demek ne demek bunu da görebiliriz.

Şimdi, aynı işlemi yapan bir fonksiyonu C’de oluşturmaya çalışacağız. Ancak, bu kez, öz-yineleme'nin ayrıntılarını Python örneğinde olduğu gibi göstermeyeceğim. Çünkü, hemen hemen aynı akışı izliyor.

Geçenlerde, class konusuna yönelik sözler söylerken, soyutlama kavramından bahsetmiştik. Python, C’ye göre oldukça soyuttur. Bu cümle, Python’ın C’ye göre daha karmaşık olduğu anlamına da gelir. C dilindeki kavramların esneklikleri, Python’daki kadar fazla değildir. Örneğin, C’de tipleri açıkça tanımlamak zorundayız. Her bir değişkenin belirli bir tipi olur ve o değişken yalnızca o tipe ait değerler edinebilir. Python ise bu konuda esnektir; bir değişkenin, herhangi bir tipi ve herhangi bir tipe ait bir değeri olabilir. Mantık olarak bakarsanız, değişken kavramına Python dilinde daha çok yaklaştığımızı görebiliriz.

Devam etmeden önce, bir husustan bahsedeyim. C’de bir dizinin boyutunu açıkça ölçebilmek her zaman mümkün olmayabilir. Açıkça dizi tanımlamak yerine, dizi gibi davranan pointerlar tanımlayıp, o pointerlar ile işlemler yapmak C’de yaygın olarak izlenen yaklaşımlardan birisidir. Ancak bir dizinin nerede bittiğini tespit etmek Python’daki gibi kolay olmayabilir.

Madem öyle, yavaş yavaş kodlamaya geçelim:

Önce gerekli kütüphaneleri programa aktaralım. Değişkenlere hafızadan yer ayırmak için stdlib.h kütüphanesindeki malloc fonksiyonuna ve ayrılmış hafızayı serbest bırakmak için de free fonksiyonuna ihtiyacımız olacak. Sonuçları görebilmek için de stdio.h kütüphanesindeki printf fonksiyonunu kullanacağız.

#include <stdio.h>
#include <stdlib.h>

Python’da oluşturduğumuza benzer bir fonksiyon oluşturmaya çalışacağız şimdi. Python’da fonksiyonumuz 2 boyutlu bir dizi geri döndürüyordu. O zaman biz de 2 boyutlu bir dizi döndüren bir fonksiyon tanımlayalım.

C’de diziler pointer ile gösterilebilir demiştik. Peki, elemanları int tipinde olan tek boyutlu bir pointer’ı nasıl ifade edebiliriz?

int *arr = (int*)malloc(n * sizeof(int))

Yukardaki ifade, n kadar int veri tipindeki verinin kapladığı alanın hafızadan ayrılacağını ve bu ayrılmış alanın da arr isminde bir pointer tarafından işaret edileceğini gösterir. Bu ifadeyi yazdıktan sonra, hafızadan yer ayırma işleminin gerçekleşip gerçekleşmediğini kontrol etmek gerekir.

if (arr == NULL) {
    printf("Bellek Hatası!\n");
}

Yukarda, tek boyutlu bir diziye işaret eden bir pointer tanımlamak için nasıl bir ifade yazmamız gerektiğini gördük. Peki dizimiz 2 boyutlu olsaydı nasıl yapardık?

int **arr = (int**)malloc(n * sizeof(int*))

Yukardaki ifadeyi biraz açayım. Burada, elemanları pointer olan bir pointer tanımlıyoruz. Ama gördüğünüz gibi, pointer’ın işaret ettiği pointer’lar için hafızadan ne kadarlık yer ayırmamız gerektiğini henüz tanımlamadık. Peki her bir pointer’ın hafızada ne kadarlık yer kaplaması gerektiğini nasıl hesaplayacağız? Dahası yukardaki n sayısını nasıl bulacağız?

C’de işleri istenen hale getirmek için, tekerleği yeniden icat etmek gerekir.

n değeri, arr = [1, 2, 3, 4] ve r = 2 için 6 olmalıdır. Çünkü 4 elemanlı bir kümenin 2 elemanlı alt küme sayısı 6’dır. O halde, bir fonksiyon yazalım. Bu fonksiyon hafıza ayırırken ihtiyaç duyacağımız n değerini bulsun.

Normalde önce bir tane faktöryel fonksiyonu yazıp, sonra bu faktöryel fonksiyonunu kullanan bir kombinasyon fonksiyonu yazabilirdik.

int factorial(int n) {
    return (n == 0) ? 1 : n * factorial(n - 1);
}

int combination(int n, int r) {
    return factorial(n) / factorial(n - r) / factorial(r);
}

Ancak işlem karmaşıklığı açısından ondan az karmaşık başka bir algoritma kullanalım:

int combination(int n, int r) {
    int res = 1;
    for (int i = 0; i < r; i++) {
        res *= (n - i);
        res /= (i + 1);
    }
    return res;
}

O halde, 2 boyutlu dizimizin uzunluğunu hesaplayabiliriz artık. Bu arada artık yavaş yavaş combinations fonksiyonunu oluşturmaya başlayabiliriz sanırım.

Fonksiyonumuz 2 boyutlu bir dizi döndürecek, argüman olarak da; tek boyutlu, elemanları int olan bir dizi ve 2 adet integer değer alacak.

int **combinations(int *arr, int n, int r) {

}

Yukardaki fonksiyona daha sonradan argümanları biz şöyle vereceğiz:

int arr[] = {1, 2, 3, 4};
int n = sizeof(arr) / sizeof(int);
int r = 2;
combinations(arr, n, r);

combinations fonksiyonunun içini doldurmaya devam edelim:

int **combinations(int *arr, int n, int r) {
    int **combs = (int**)malloc(combination(n, r) * sizeof(int*);
}

Anlaşılır oldu değil mi? arr kümesinin r elemanlı alt kümeleri neler bunları bulmadan önce, kaç tane alt küme oluşturacağımızı combination(n, r) ile hesaplarız. Bu fonksiyondan dönen değer ile de sizeof(int*)'i çarparsak, kaç tane pointer için yer ayrılması gerektiğini belirlemiş oluruz. Bu pointerlar da bildiğiniz gibi bir serideki sayılara işaret ediyorlar. O halde, şimdi, her bir pointerımızı temsil etmesi için r uzunluğunda 1 boyutlu bir pointer tanımlayalım. Her birisi için böyle bir tane pointer tanımlamaya gerek yok. Bir tane tanımlayıp, pointer’ın işaret ettiği değeri değiştirmek yeterli olur.

int **combinations(int *arr, int n, int r) {
    int **combs = (int**)malloc(combination(n, r) * sizeof(int*);
    int *comb = (int*)malloc(r * sizeof(int));
}

Tabi, hafızadan yer ayırma işlemi başarılı oldu mu olmadı mı kontrol etmemiz gerekiyor.

int **combinations(int *arr, int n, int r) {
    int **combs = (int**)malloc(combination(n, r) * sizeof(int*);
    int *comb = (int*)malloc(r * sizeof(int));
    if (combs == NULL || comb == NULL) {
        printf("Bellek Hatası!\n");
        return NULL;
    }
}

combs ve comb pointerlarını bulacağımız kombinasyonları hafızada tutsunlar diye oluşturduk. Peki ama kombinasyonları bulma işlemini nasıl yapacağız? Python’daki öz-yineleme nasıldı hatırlamaya çalışalım.

Fonksiyon çalışmaya başlıyordu, r ile ilgili bir sorgu yapılıyordu sonra da arr üzerinde bir döngü kuruluyordu ve her bir elemana göre de öz-yineleme gerçekleşiyordu. Burada da aşağı yukarı benzer bir şey yapacağız. Ama C’de Python’daki gibi indeksleme kolaylığı yoktur. Yani a[1:] şeklinde dilimleme yapamayız. Ayrıca Python’daki yield deyimi gibi çalışan bir deyim de yoktur. Ve * işaretinin Python’daki ‘diziyi açma’ rolü, C’de mevcut değildir.

O zaman nasıl yapacağız?

Şimdi biz temelde ne yapmaya çalışıyoruz? Bir takım hesaplamalar sonucunda, 2 boyutlu bir diziye eleman eklemeye çalışıyoruz. 2 boyutlu bir dizi demek, satır ve sütunlar demektir. Bir önceki Python örneğinde gördüğümüz gibi, r sayısı, bizim her bir kombinasyonun uzunluğunu veren sayı. O halde r, sütun sayısına karşılık geliyor. combination(n, r) ifadesi ile n elemanlı bir kümenin r elemanlı alt küme sayısını buluyorduk. Bu alt küme sayısı da satır sayısına karşılık geliyor.

Şu ana kadar, 2 tane değişken saydım. Satır sayısı ve sütun sayısı. Öz-yinelemede bunlar için parametre yazmamız gerekecek.

Sonra, arr dizisinin içinde dolaşmak için de ayrıca bir parametreye ihtiyacımız olacak.

O halde, öz-yineleme fonksiyonunda şu parametrelerin olması gerekiyor:

  1. combs’a veri eklemek için, int **combs adında bir parametre,
  2. comb’a veri eklemek için int *comb adında bir parametre,
  3. arr’dan veri çekebilmek için int *arr adında bir parametre,
  4. arr’ın uzunluğu için int n adında bir parametre,
  5. Alt kümelerin eleman sayısı için int r adında bir parametre,
  6. combs’a veri eklemek için int row adında bir parametre,
  7. comb’a veri eklemek için int col adında bir parametre,
  8. arr’da dolaşmak için int index adında bir parametre

Yukarda, hangi parametreye hangi amaçla ihtiyaç duyulacağı umarım yeterince anlaşılırdır. Şimdi, öz-yineleme fonksiyonumuzu oluşturmaya başlayalım:

int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {

}

Şimdi fonksiyonun içini doldurmaya başlayalım. Python fonksiyonunda yazdığımız sorguya benzer bir sorguyu burada da yazacağız. Bu kez r'yi sıfırlamayacağız, bunun yerine col değişkenimizi artıracağız ve col değişkeni r'ye eşit olunca fonksiyon öz-yinelemeyi durduracak.

int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {
    if (col == r) {

    }
}

Python fonksiyonunda bu durumda boş bir tuple verisini üretip, öz-yinelemeyi sonlandırıyorduk. Ama bizim C'de yield gibi bir ifademiz yok. Dolayısıyla ne burada, ne de döngülerin altında, yield deyimini kullanamayacağız. Peki ne yapacağız? col == index durumunda, pointerlara hangi değerleri işaret etmeleri gerektiğinin atamalarını yapacağız. Çünkü yukardaki Python fonksiyonundaki adımları açarken göstermeye çalıştığım gibi, r, bir çok defa (aslında tam olarak combination(n, r) kere) sıfırlanıyor. Burada da bir çok defa (combination(n, r) kere) col == index durumuyla karşılaşacağız. Dolayısıyla bu koşulun sağlandığı durumlarda, öz-yineleme sonlanacak ve pointerların işaret ettiği değerleri kaybedeceğiz. O halde bu kısımda atama işlemlerini yapmamız gerekiyor.

Atama işlemlerini de dahil ederek fonksiyonu tekrar yazıyorum:

int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index) {
    if (col == r) {
        combs[row] = (int*)malloc(r * sizeof(int));
        if (combs[row] == NULL) {
            printf("Bellek Hatası!");
            return 1;
        }
        for (int j = 0; j < r; j++) {
            combs[row][j] = comb[j];
        }
        return row + 1;
    }
}

Şimdi burada ne yaptık?

Daha önce combs pointerlara işaret eden bir pointer demiştik değil mi? Bu pointerların hafızada ne kadarlık yer ayırması gerektiğini hesaplamamıştık hatırlıyorsanız. Şimdi bunları hesaplama sırası geldi.

combs[row] = (int*)malloc(r * sizeof(int)); ifadesi ile combs'un ilgili satırı için yeterli alanı ayırıyoruz. Sonra hafıza ayırma işlemi yapılmış mı yapılmamış mı kontrol ediyoruz.

Daha sonra da, comb'dan verileri alıp, combs'un ilgili satırının ilgili sütununa ekliyoruz. Sonra da row'u 1 birim artırıp geri döndürüyoruz. O halde fonksiyonun geri kalan kısmında da comb'a ekleme yapmamız gerek. Fonksiyonu doldurmaya devam edelim.

int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index)  {
    if (col == r) {
        combs[row] = (int*)malloc(r * sizeof(int));
        if (combs[row] == NULL) {
            printf("Bellek Hatası!");
            return 1;
        }
        for (int j = 0; j < r; j++) {
            combs[row][j] = comb[j];
        }
        return row + 1;
    }
    for (int i = index; i < n; i++) {
        comb[col] = arr[i];
        row = recursive_combinations(combs, comb, arr, n, r, row, col + 1, i + 1);
    }
    return row;
}

Burada, neden ilk kısımda, row + 1'i döndürdük, alttaki döngüde neden öz-yinelemeden dönen sonucu row'a eşitledik anlamaya çalışalım. Madem ki, öz-yineleme durunca veriler kaybolacak, row'u artırıp, yeni değeri haline getirmezsek, hep 0. satıra ekleme yaparız. O halde satır sayısını artırmamız lazım. Öz-yineleme durduğunda row = row + 1 olur ve yani col = r olduğunda, row 1 birim artmış olur.

Python’daki fonksiyonumuz ile benzer bir algoritmaya sahip bir algoritmayı C’de bu şekilde yazabiliriz. Şimdi sıra geldi, daha önce yazdığımız int **combinations fonksiyonunda int recursive_combinations fonksiyonunu çağırmaya.

Fonksiyonun geri kalan kısmını tamamlayalım. Ama önce, bütün işlemler bittikten sonra yapmamız gereken işlemleri yapacak olan, hafızayı serbest bırakma fonksiyonunu yazalım.

void free_memory(int **arr, int length) {
    for (int i = 0; i < length; i++) {
        free(arr[i]);
    }
    free(arr);
}

Buradaki free fonksiyonu stdlib.h'dan gelen fonksiyondur.

Şimdi, int **combinations fonksiyonunun kalan kısmını yazabiliriz.

int **combinations(int *arr, int n, int r) {
    int **combs = (int **)malloc(combination(n, r) * sizeof(int *));
    int *comb = (int *)malloc(r * sizeof(int));
    if (combs == NULL || comb == NULL) {
        printf("Bellek Hatası!\n");
        return NULL;
    }
    recursive_combination(combs, comb, arr, n, r, 0, 0, 0);
    free(comb);
    return combs;
}

Evet, programımızı oluşturduk ama bir test yapalım hemen. Aşağıda, main fonksiyonu eklenmiş bir program görüyorsunuz. Bu programı derleyip çalıştırabilirsiniz. Bu arada sadece combination fonksiyonundan dönen değerin tipini int'ten unsigned long long'a çeviriyorum ki yüksek n değerlerinde hafıza problemi yaşamayalım.

#include <stdio.h>
#include <stdlib.h>

unsigned long long combination(int n, int r) {
    unsigned long long result = 1;
    for (int i = 0; i < r; i++) {
        result *= (n - i);
        result /= (i + 1);
    }
    return result;
}

int recursive_combination(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index)  {
    if (col == r) {
        combs[row] = (int*)malloc(r * sizeof(int));
        if (combs[row] == NULL) {
            printf("Bellek Hatası!");
            return 1;
        }
        for (int j = 0; j < r; j++) {
            combs[row][j] = comb[j];
            printf("%d ", combs[row][j]);
        }
        printf("\n");
        return row + 1;
    }
    for (int i = index; i < n; i++) {
        comb[col] = arr[i];
        row = recursive_combination(combs, comb, arr, n, r, row, col + 1, i + 1);
    }
    return row;
}

int **combinations(int *arr, int n, int r) {
    int **combs = (int **)malloc(combination(n, r) * sizeof(int *));
    int *comb = (int *)malloc(r * sizeof(int));
    if (combs == NULL || comb == NULL) {
        printf("Bellek Hatası!\n");
        return NULL;
    }
    recursive_combination(combs, comb, arr, n, r, 0, 0, 0);
    free(comb);
    return combs;
}

void free_memory(int **arr, int len) {
    for (int i = 0; i < len; i++) {
        free(arr[i]);
    }
    free(arr);
}

int main() {
    int arr[] = {1, 2, 3, 4, 5, 6};
    int n = sizeof(arr) / sizeof(int);
    int r = 2;
    int **test = combinations(arr, n, r);
    free(test);
    return 0;
}

Bu kodu derleyip çalıştırdığımızda şöyle bir çıktı alırız:

1 2
1 3
1 4
1 5
1 6
2 3
2 4
2 5
2 6
3 4
3 5
3 6
4 5
4 6
5 6

Evet, Python’da yazdığımız fonksiyonu C’de de yazdık. Şimdi şöyle bir şey yapmak istiyorum. Bir struct oluşturmak istiyorum. Bu struct, hem arr dizisini, hem n değerini, hem r değerini, hem de kombinasyonların değerlerini tutsun. Sonra da bu struct'u Python’da kullanalım.

Şöyle bir struct tanımlıyorum.

typedef struct class_combinations {
    int *arr;
    int n;
    int r;
    unsigned long long len;
    int **combinations;

} class_combinations;

Buradaki len niteliği, combination(n, r)'den gelen değer olacak. O halde örnek yapıcı fonksiyonumuzu oluşturmaya geçelim şimdi. Şimdi, istersek, doğrudan class_combinations yapısını döndüren bir fonksiyon yazabiliriz. Veya bu struct'u işaret eden bir pointer'ı döndüren bir fonksiyon da yazabiliriz.

Aşağıda, class_combinations yapısını işaret eden bir pointer'ı döndüren bir fonksiyon yazdık, adına da Combinations dedik.

class_combinations *Combinations(int *arr, int n, int r) {
    class_combinations *self = (class_combinations*)malloc(sizeof(class_combinations));
    if (self == NULL) {
        printf("Bellek Hatası!");
        return NULL;
    }
    unsigned long long len = combination(n, r);
    int **data = combinations(arr, n, r);
    self -> arr = arr;
    self -> n = n;
    self -> r = r;
    self -> len = len;
    self -> combinations = data;
    return self;
}

Burada, class_combinations ifadesinin artık int veya char gibi bir tipe karşılık geldiğini dikkatiniz çekmiştir sanıyorum. Dolayısıyla class_combinations *Combinations() ifadesi, class_combinations sınıfından bir veriyi tutan bir pointerı döndüren bir fonksiyondur.

Şimdi, sıra geldi hazırladığımız bu struct'ü Python’a aktarmaya. Önce bir tane paylaşılabilir bağlantı kitaplığı oluşturalım.

Python scriptine ctypes’ı import edelim.

import ctypes

Sonra da, C'de tanımladığımız struct'un verilerini aktaracağımız, ctypes.Structure sınıfını miras alan bir sınıf ve bu sınıfın niteliklerini tanımlayalım.

import ctypes


class Combinations(ctypes.Structure):
    _fields_ = [
        ("arr", ctypes.POINTER(ctypes.c_int)),
        ("n", ctypes.c_int),
        ("r", ctypes.c_int),
        ("len", ctypes.c_ulonglong),
        ("combinations", ctypes.POINTER(ctypes.POINTER(ctypes.c_int)))
    ]

C’de yazdığımız struct'a benzer bir structu Python’da bu şekilde yazarız.

Şimdi, sıra geldi, dinamik bağlantı kitaplığımızı oluşturmaya, bu kitaplığı şöyle oluşturuyoruz:

gcc -shared -o combinations.dll combinations.c

Burada c dosyanın adının combinations.c olduğu varsayılmıştır. Neyse, dinamik bağlantı kitaplığımız oluştu. Python kodlarını yazmaya devam edelim. Önce kitaplığı okutalım.

lib = ctypes.CDLL("./combinations.dll")

Şimdi, Combinations fonksiyonunun argüman tiplerini ve ne tip bir değer döndürdüğünü belirtelim. Ayırca, free_memory fonksiyonunun da argüman tiplerini tanımlayalım ki, sonradan bu fonksiyonu kullanarak, hafızayı serbest bırakalım.

lib = ctypes.CDLL("./combinations.dll")
lib.Combinations.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_int]
lib.Combinations.restype = ctypes.POINTER(Combinations)
lib.free_memory.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_int)), ctypes.c_int]

Biraz açıklamak gerekirse, Python’daki ctypes.POINTER(ctypes.c_int), C’deki (int*) ifadesi ile eşleşir. ctypes.POINTER(ctypes.POINTER(ctypes.c_int)) ifadesi de (int**) ifadesi ile eşleşir.

Şimdi, hemen hemen herşeyi hazırladık. Fonksiyonumuzu birazdan kullanabiliriz ama önce ufak bir sorunu da halletmemiz lazım.

Biz bir Python listesini, bir pointer olarak nasıl kullanacağız?

Onu da aşağıdaki kodda gösterelim:

# arr = range(1, 5)
# n = len(arr)
# r = 2
# c_arr = (ctypes.c_int * n)(*arr)

Gördüğünüz gibi, arr'ı, (ctypes.c_int * n)(*arr) ifadesiyle bir pointer’a çevirdik. Şimdi kodun geri kalan kısmını yazalım:

arr = range(1, 5)
n = len(arr)
r = 2
c_arr = (ctypes.c_int * n)(*arr)

test = lib.Combinations(c_arr, n, r)

for i in range(test.contents.len):
    for j in range(test.contents.r):
        print(test.contents.combinations[i][j], end=" ")
    print()

lib.free_memory(test.contents.combinations, test.contents.len)

Son yazdığımız Python kodunu düzgün bir şekilde yazalım:

import ctypes


class Combinations(ctypes.Structure):
    _fields_ = [
        ("arr", ctypes.POINTER(ctypes.c_int)),
        ("n", ctypes.c_int),
        ("r", ctypes.c_int),
        ("len", ctypes.c_ulonglong),
        ("combinations", ctypes.POINTER(ctypes.POINTER(ctypes.c_int)))
    ]


lib = ctypes.CDLL("./combinations.dll")
lib.Combinations.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_int]
lib.Combinations.restype = ctypes.POINTER(Combinations)
lib.free_memory.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_int)), ctypes.c_int]

arr = range(1, 5)
n = len(arr)
r = 2
c_arr = (ctypes.c_int * n)(*arr)

test = lib.Combinations(c_arr, n, r)

for i in range(test.contents.len):
    for j in range(test.contents.r):
        print(test.contents.combinations[i][j], end=" ")
    print()

lib.free_memory(test.contents.combinations, test.contents.len)

Bu örnekte, C’de yazdığımız struct’a işaret eden bir pointer kullandık. Peki struct'u kaldırıp, sadece fonksiyonu kullanmak isteseydik?

#include <stdio.h>
#include <stdlib.h>

unsigned long long combination(int n, int r) {
    unsigned long long result = 1;
    for (int i = 0; i < r; i++) {
        result *= (n - i);
        result /= (i + 1);
    }
    return result;
}

int recursive_combination(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index)  {
    if (col == r) {
        combs[row] = (int*)malloc(r * sizeof(int));
        if (combs[row] == NULL) {
            printf("Bellek Hatası!\n");
            return 1;
        }
        for (int j = 0; j < r; j++) {
            combs[row][j] = comb[j];
        }
        return row + 1;
    }
    for (int i = index; i < n; i++) {
        comb[col] = arr[i];
        row = recursive_combination(combs, comb, arr, n, r, row, col + 1, i + 1);
    }
    return row;
}

int **combinations(int *arr, int n, int r) {
    int **combs = (int **)malloc(combination(n, r) * sizeof(int *));
    int *comb = (int *)malloc(r * sizeof(int));
    if (combs == NULL || comb == NULL) {
        printf("Bellek Hatası!\n");
        return NULL;
    }
    recursive_combination(combs, comb, arr, n, r, 0, 0, 0);
    free(comb);
    return combs;
}

void free_memory(int **arr, int len) {
    for (int i = 0; i < len; i++) {
        free(arr[i]);
    }
    free(arr);
}

Bunu da Python’a şöyle aktarırdık:

import ctypes

lib = ctypes.CDLL("./combinations.dll")
lib.combinations.argtypes = [ctypes.POINTER(ctypes.c_int), ctypes.c_int, ctypes.c_int]
lib.combinations.restype = ctypes.POINTER(ctypes.POINTER(ctypes.c_int))
lib.free_memory.argtypes = [ctypes.POINTER(ctypes.POINTER(ctypes.c_int)), ctypes.c_int]

arr = range(1, 5)
n = len(arr)
r = 2
c_arr = (ctypes.c_int * n)(*arr)

test = lib.combinations(c_arr, n, r)
print(test[0][0])

Evet arkadaşlar, buraya kadar anlattıklarım buzdağının görünen kısmından ibaret. Konuyu mümkün olduğunca açmaya çalıştım. Elbette değinmediğim ayrıntılar da var. Eğer, benzer bir katkı vermek isterseniz, konudan devam edebilirsiniz.

Okuduğunuz için teşekkürler, hatalı kısımları bildirirseniz, o kısımları düzeltiriz.

Herkese iyi çalışmalar.

8 Beğeni

elinize sağlık hocam güzel içerik

Rica ederim,

İzninizle, sonradan farkettiğim, istenmeyen bir davranış oluşturmayan ama gereksiz bir iş yükü oluşturan bir kısmı düzeltip, kodları tekrar yazayım.

C’deki struct'u şöyle oluşturmuştuk, hatırlarsanız:

class_combinations *Combinations(int *arr, int n, int r) {
    class_combinations *self = (class_combinations*)malloc(sizeof(class_combinations));
    if (self == NULL) {
        printf("Bellek Hatası!");
        return NULL;
    }
    unsigned long long len = combination(n, r);
    int **data = combinations(arr, n, r);
    self -> arr = arr;
    self -> n = n;
    self -> r = r;
    self -> len = len;
    self -> combinations = data;
    return self;
}

Peki int **combinations(arr, n, r) fonksiyonu nasıldı?

int **combinations(int *arr, int n, int r) {
    int **combs = (int **)malloc(combination(n, r) * sizeof(int *));
    int *comb = (int *)malloc(r * sizeof(int));
    if (combs == NULL || comb == NULL) {
        printf("Bellek Hatası!\n");
        return NULL;
    }
    recursive_combination(combs, comb, arr, n, r, 0, 0, 0);
    free(comb);
    return combs;
}

Bu iki koda bakarsak, aslında struct'un len niteliği için bir kez ve kombinasyonlar hesaplanırken ikinci kez, combination(n, r) fonksiyonu çağrılıyor, ayrıca data’ya da gerek yok. Gereksiz bir işlem yükü. Bunları kaldıralım.

int **combinations fonksiyonuna len isminde yeni bir parametre ekliyorum:

int **combinations(int *arr, int n, int r, unsigned long long len) {
    int **combs = (int **)malloc(len * sizeof(int *));
    int *comb = (int *)malloc(r * sizeof(int));
    if (combs == NULL || comb == NULL) {
        printf("Bellek Hatası!\n");
        return NULL;
    }
    recursive_combination(combs, comb, arr, n, r, 0, 0, 0);
    free(comb);
    return combs;
}

class_combinations *Combinations fonksiyonunu da şöyle değiştirelim:

class_combinations *Combinations(int *arr, int n, int r) {
    class_combinations *self = (class_combinations*)malloc(sizeof(class_combinations));
    if (self == NULL) {
        printf("Bellek Hatası!");
        return NULL;
    }
    self -> arr = arr;
    self -> n = n;
    self -> r = r;
    self -> len = combination(n, r);
    self -> combinations = combinations(arr, n, r, self -> len);
    return self;
}

Tamamdır. Artık, len için bulunan değeri, int **combinations() fonksiyonuna argüman olarak verip, kombinasyonun ikinci kez hesaplanmasının önüne geçeriz. Ayrıca int **data'ya da gerek yok. Onu da kaldırdım.

C, kodlarının en güncel hali şöyle:

#include <stdio.h>
#include <stdlib.h>

void free_memory(int **arr, int len) {
    for (int i = 0; i < len; i++) {
        free(arr[i]);
    }
    free(arr);
}

unsigned long long combination(int n, int r) {
    unsigned long long result = 1;
    for (int i = 0; i < r; i++) {
        result *= (n - i);
        result /= (i + 1);
    }
    return result;
}

int recursive_combinations(int **combs, int *comb, int *arr, int n, int r, int row, int col, int index)  {
    if (col == r) {
        combs[row] = (int*)malloc(r * sizeof(int));
        if (combs[row] == NULL) {
            printf("Bellek Hatası!");
            return 1;
        }
        for (int j = 0; j < r; j++) {
            combs[row][j] = comb[j];
            printf("%d ", combs[row][j]);
        }
        printf("\n");
        return row + 1;
    }
    for (int i = index; i < n; i++) {
        comb[col] = arr[i];
        row = recursive_combinations(combs, comb, arr, n, r, row, col + 1, i + 1);
    }
    return row;
}

int **combinations(int *arr, int n, int r, unsigned long long len) {
    int **combs = (int **)malloc(len * sizeof(int *));
    int *comb = (int *)malloc(r * sizeof(int));
    if (combs == NULL || comb == NULL) {
        printf("Bellek Hatası!\n");
        return NULL;
    }
    recursive_combinations(combs, comb, arr, n, r, 0, 0, 0);
    free(comb);
    return combs;
}

typedef struct class_combinations {
    int *arr;
    int n;
    int r;
    unsigned long long len;
    int **combinations;
} class_combinations;

class_combinations *Combinations(int *arr, int n, int r) {
    class_combinations *self = (class_combinations*)malloc(sizeof(class_combinations));
    if (self == NULL) {
        printf("Bellek Hatası!");
        return NULL;
    }
    self -> arr = arr;
    self -> n = n;
    self -> r = r;
    self -> len = combination(n, r);
    self -> combinations = combinations(arr, n, r, self -> len);
    return self;
}

int main() {
    int arr[] = {1, 2, 3, 4, 5, 6};
    int n = sizeof(arr) / sizeof(int);
    int r = 2;
    class_combinations *test = Combinations(arr, n, r);
    free(test);
    return 0;
}
1 Beğeni

Elinize sağlık.

Kitabın ortası olmuş.

Ben de kitabın ortasından sorup, kitabın ortasından yazayım.

Yine de iddianız , çok iddialı.

C ile python ın farklılıklarını konuşmak mı?

Benzerlikleri konuşsak daha kısa sürmez miydi?

Bir başka soru, neden python ile c dilini birlikte kullanayım?

Kafamda sorular varken metni takip edeyim…

Bakalım c ve python birlikte nasıl kullanılırmış…

Ama birlikte kullanılmış gibi görünmedi gözüme.

Mesela bir işletim sistemim var ve o .dll nedir bilmez.

Bu sadece Windows’un ortaya attığı dinamik kod paylaşımı sağlayan bir windows yöntemi.

Bu iki dili kullanmak gibi görünmedi gözüme.

Mesela ben şöyle hayal etmiştim.

Çoğu C editör, derleyici lerinin içinde bir inline assembler bulunur.

Yani siz bir _asm direktifiyle, doğrudan C kodunuzun içerisine assembly dilinde kodlar yazarsınız ve derleyebilirsiniz.

Ben buna iki dili bir arada kullanmak derim.

Ama dönüyorum, sizin yazdıklarınıza.

Elinizde oluşturulmuş bir .dll dosyası var ve biz buna windows un sunduğu bellek erişim metodu ile runtime erişiyoruz.

Ki bu sadece “Windows” ’ un sunduğu bir yöntem.

Yani konu Linux olsaydı ne yapcaktık?

Anatomy of Linux dynamic libraries - IBM Developer

Yani her işletim sisteminin bir dinamik kütüphane yaklaşımı var.

Ve her dinamik kütüphane yaklaşımı olan işletim sisteminin, dinamik kütüphanenin hangi dilde kodlandığını dert etmeden pyhon ile çağırıp kullanılabilirliği var.

Bu durumda neden konu C ile Python’un beraber kullanımı gibi lanse edilmiş anlamaya çalışıyorum.

Yani kafamda deli sorular.

Bunlar burada duradursun soru olarak.

Diğer sorularımı sıralayayım:

Neden derlenen bir dille yorumlanan bir dili karşılaştarayım?

Neden low level yada high level arasında gidip gelen C gibi mid-level diyebileceğim bir dili Python gibi High level dil ile karşılaştırayım?

Neden yapısal programlama için tasarlanmış C dili ile NYP için tasarlanmış Python gibi bir dili karşılaştırayım.

Yani zaten karşılaştırmaya çalıştığınız bir çok kısımda birbirlerinde çok farklılar.

Yada karşılaştırırken python fonksiyonlarında yerleşik yada harici kütüphanelerini kullanırken, C’de benzer işler için kütüphaneleri include etmek yerine kodunu yazarak karşılaştırayım?

Mesela itertools import edip C de buna karşılık bir kütüphane include etmek yerine neden kodunu yazıyoruz?

Yada neden C++ kullanıp, string nesneleri üzerinde daha esnek seçenekler sunan kütüphaneleri include etmek yerine C’nin amacı bilinen kısıtlı veri metodlarını kullanarak Python listeleriyle karşılaştırıyoruz? ( Burada kısıtlılık aslında esneklik sebepleri çok ve programcıya kendi metodlarını oluşturma özgürlüğü tanıdığından eksiklik olarak kullanılmamıştır.)

Yani yazının özeti, Python ile yazılan kodlarla dinamik kütüphanelere nasıl erişilebilir olabilirdi.

Karşılaştırma zor, beraber kullanma zor ve kafamda bir yığın soru da aha duruyor ama vaktim kalmadı.

Kolay gelsin.

1 Beğeni

Bence değil, hedeflediğime ulaşıp ulaşmadığım konusunda başka değerlendirmeler de vardır sanırım. Ayrıca alıntıladığınız kısmı inceliyorum ama “çok mu iddialı” bir üslupla konuşmuşum diye soruyorum. Çok mu iddialı gerçekten? Meşhur farklılıkları biraz olsun tanıtabilmiş miyim, bu iki dili birlikte nasıl kullanabiliriz gösterebilmiş miyim? Var mı başka bu konuda yorum yapmak isteyen arkadaş? :slight_smile:

Bilmem, acelem yok.

Performans için olabilir.

Ben Ubuntu’da da benzer bir şekilde dinamik bağlantı kullanıyorum, orada .dll yok ama .so var. Aynı işlevi görüyorlar. Yani sadece Windows kullanıcısına yönelik bir Windows yöntemi değil bu. Hatta istersen Linux’te dinamik bağlantı oluştururken uzantılı bir şekilde oluştur. Uzantısı da DLL olsun, yine çalıştırırsın.

Konu Windows da değil ki, Linux olsun. Linux’te ayrı Windows’ta ayrı şekilde çalıştırmamız gereken bir örnek değil abi. Windows kullanıcısı da, Linux kullanıcısı da benzer bir yolla burada anlattığım programı derler. Atla deve değil…

Python’da yazdığımız bir kodun C dilindeki eşleniğini yazıp sonra da bunu dinamik bağlantı kitaplığı yoluyla Python’da kullandık. Programı çalıştıran kodlar, hem Python, hem de C’den geldiği için, hem Python dili kodları hem de C dili kodları aynı projede kullanılmış olurlar.

Bilgilenmek için?

Bir dilde anlattığımız bir hikayeyi, diğer dilde de aynı anlama gelecek şekilde anlatmak istediğimizde nelere ihtiyaç duyuyoruz. Hikayeyi nasıl kuruyoruz, bunları öğrenmek için.

Sonuçta her ikisi de bir dil. Her iki dilde de belirli anlamlara gelen cümleler, cümlecikler var. Bir dilden diğer dile çeviri yaparken, bazen karşılaştırma da yapıyoruz.

:slight_smile:

Burada karşılaştırılan şey, algoritmaların dilin özelliklerinden ötürü nasıl değiştiği.

Abi dikkatini çekeyim,

Mesela ne diyorum:

Python’da dizi[::-1] gibi dizi dilimleme işlemleri yapabiliriz ama C’de bunu yapamayız diyorum. Bu dilimleme özelliği yerine, benzer bir işlemi C’de nasıl yapabiliriz tam da bunu anlatmaya çalışıyorum.

Diyorum ki, Python’da yield var, hangi amaçla kullanıldığını da biliyorsunuz. Sonra diyorum ki, C’de bu özellik de yok. Ama sorun mu, değil? Biz yield’ın yokluğunu aratmayacağız… Sadece programlama yaklaşımımızı değiştireceğiz. Benim burada yapmaya çalıştığım şey buydu. Sonra da C’de yazdığımız uygulamayı, ctypes bağlantısı ile Python’da kullanmaya başladık.

Python’da yazdığım combinations fonksiyonunda herhangi bir kütüphane kullanmadım ki. Nasıl okuyorsun yazıyı anlayamadım. Herhangi bir kütüphane kullanılmamış bir çalışma için şöyle yazıyorsun:

python fonksiyonlarında yerleşik yada harici kütüphanelerini kullanırken, C’de benzer işler için kütüphaneleri include etmek yerine kodunu yazarak karşılaştırayım?

Hani abi yerleşik kütüphane?

def combinations(arr, r):
    if not r:
        yield ()
        return
    for i in range(len(arr)):
        for comb in combinations(arr[i + 1:], r - 1):
            yield arr[i], *comb


print(*combinations(arr, 2))

Abi, sen bir baştan mı okusan ne yapsan. Gözünden kaçmış belli ki. Herhangi bir kütüphane kullanmadık…

Kütüphane karşılaştırmadık burada. Farklı bir algoritmayla itertools.combinations’ın yaptığı işin benzerini yapan bir Python fonksiyonu yazdık. Ve bunda da herhangi bir kütüphane kullanmadık. Sonra da dedik ki biz bu algoritmayı C’de de uygulamak istesek, ne tür değişiklikler yapmamız gerekir.

4 Beğeni

Çok uzun. kısaca yazayım.

Uzunca irdelerim.

C’de girdi çıktı yapmak için bile bir kütüphane eklemek gerekiyorken python ile nasıl karşılaştıralım.

Birlikte kullanmak kısmını gözden kaçırmışsın, bir de o kısmı tekrar bakmak isteyebilirsin.

C için yanlış düşünüyorsun, C sana o kadar esnek bir alan bırakır ki kendin tanımlayarak

Gibi bir işlemi istediğin gibi tanımlayabilirsin. Zaten sorun da tam da bu. C bunu canın nasıl isterse oluşturabil diye burada yerleşik bir kural koymuyor.

Uzuunca yazarız. Yalnız kavramsal olarak bazı yerleri henüz görmediğini düşünüyorum.

Bir de inline assembler kısmına odaklan istersen yazımda. Kolay gelsin.

1 Beğeni

Ben de onu anlatıyordum… Tekerleği sıfırdan icat etmeniz gerekir yazmıştım ya hani…

Bu yüzden diyorum nasıl okudun yazıyı diye.

Başlığa bir bakar mısın? Python diliyle programlamaya aşina olanlar için C diline dair bilinmesi gerekenler diye bir başlık açıyorum. Sonra Python’da kullanabildiğimiz bazı şeyleri C’de kullanamayacağımızı anlatıyorum. Sen de diyorsun ki, “C esnek, C’de de bunu kendin tasarlayabilirsin.” E yazıda zaten açıkça söyleniyor değil mi? Tekerleği yeniden icat etmek zorundasın diye.

3 Beğeni

Yukardan aşağı bir kez okursan neden bahsettiğimi anlamamış gibi cevaplar vermişsin.

Hele ki bu söze kesinlikle katılmıyorum.

1 Beğeni

Katılmayın. Neyse, iyi günler.

2 Beğeni

Ben hem c hem de python kullanan (daha doğrusu öğretmeye çalışan) biri olarak yazıdan memnunum ve merakla devamını bekliyorum. Kendi adıma bunu söyleyebilirim.

Geri bildiriminiz için teşekkür ederim. Konuya geri dönelim isterseniz.

Mesela, bildiğiniz gibi, Python’da şöyle listeleri hızlıca oluşturabiliyoruz:

lst = [[1, 2, 3, 4], 1]

Yukardaki ifade, insanın doğal diline daha çok yaklaşmamış mı?

Şimdi, C’ye göre düşünelim.

Bir pointer var; bir dizi elemana işaret ediyor. Bu örnekte, 2 elemana işaret ediyor, bu elemanlardan birisi, pointer, diğeri de integer tipinde. Aslına bakarsanız, böyle bir şey, C’de pek mümkün değil. Nasıl yani?

Yani, bir pointer, aynı anda hem bir pointer’a hem de bir tipe örneğin inte işaret edebilir mi? (Edemez. Bir pointer aynı anda iki pointer’a işaret edebilir, bu, iki boyutlu bir dizi oluşturur. Ama, dizinin yanında yer alan integer, kendisi bir dizi değil.)

Yani ne yapacağız?

Python’daki lst'nin değeri olarak gördüğümüz şeyi, C’de başka bir simge sistemi kullanarak göstermek zorundayız.

Peki, biz C’de nasıl bir ifade kalıbı kullanmalıyız ki, yukardaki listeyi elde edelim.

Şimdi, ilk mesajdan hatırlayacak olursanız, 2 boyutlu bir dizi tanımlamak istediğimizde şöyle bir ifade kullanıyorduk.

tip **pointerismi = malloc(uzunluk * sizeof(tip*));

O halde, şöyle yazalım.

int **lst = malloc(2 * sizeof(int*);
if (lst != NULL) {
    printf("Bellek Hatası!\n");
    free(lst);
    exit(EXIT_FAILURE);
}

Şimdi de bu pointer’ın işaret ettiği pointerlara önce hafızadan yer ayıralım. Sonra da bazı işaretçilere değerler atayalım.

lst[0] = malloc(4 * sizeof(int));
lst[1] = malloc(sizeof(int));
if (lst[0] == NULL || lst[1] == NULL) {
    printf("Bellek Hatası!\n");
    free(lst[0]);
    free(lst[1]);
    exit(EXIT_FAILURE);
}
lst[0][1] = 1;
*lst[1] = 2;

Python’daki lst'ye benzer bir veri yapısı oluşturduk mu? Kısmen de olsa evet. Neden kısmen? Yukarda, şöyle bir ifade kullanmıştım: Yani, bir pointer, aynı anda hem bir pointer'a hem de bir tipe aynı anda işaret edebilir mi?.. Dilin güncel standartlarında etmez lst[1]'i bir pointer olarak yazmak zorundayız.

Yani, eğer pointer array kullanacaksak, Python’daki [[1, 2, 3, 4], 1] ifadesi C’de [[1, 2, 3, 4], [1]] olarak karşılık bulur.

Peki ben değerlere ulaşmak istediğim zaman ulaşabilir miyim? Evet, Python’daki gibi olmasa da C’nin kendi özellikleri içinde yaparız tabi.

Peki, yukardaki örnekte, lst şöyle bir yapıdaydı, lst = [[1, 2, 3, 4], 1], ya bu sondaki 1 yerine bir string koymak isteseydik ne yapacaktık?

Eğer pointer mantığını anladıysak, şimdi yavaş yavaş void'e geçelim.

void kavramının, C’deki karşılığı “belirsiz tip”'tir.

Aşağıda belirli bir tip ataması yaptığımız bir kod var.

int **lst = malloc(2 * sizeof(int*);
if (lst == NULL) {
    printf("Bellek Hatası!\n");
    free(lst);
    exit(EXIT_FAILURE);
}

Burada açıkça zaten derleyiciye şöyle bir talimat veriyoruz;

Hafızadan 2 tane int tipinde pointer’ı işaret eden bir alan ayır, bu alanlara işaret eden işaretçiyi de tipi int olan lst isminde bir değişkene ata. Değişken diyorum çünkü lst’ye atadığımız hafızanın alanını istersek değiştirebiliriz. Neyse, yani açıkça burada derleyiciye ne tip değerler için yer ayırması gerektiğini bildiriyoruz. Örneğin, yazacağımız sayı 898999898 gibi bir sayı olursa, bu sayı ayırdığımız yerdeki boyuttan daha büyük boyuta sahip olduğu için, o hafıza alanına sığmayacak ve hafıza problemine neden olacaktır. int, C dilinde, Python’daki gibi her doğal sayıyı temsil eden bir sınıf değildir. Python’da olup da, C de olmayan soyutlamalardan bir tanesine örnek olarak bunu da gösterebiliriz.

C’deki void veri tipi, tabiri caize joker veri tipidir. Yukarda değindiğim tipte listeler oluşturmak için void tipinde pointerlar yazabiliriz.

Aşağıdaki koda bakalım:

void **lst = malloc(2 * sizeof(void*));
if (lst == NULL) {
    printf("Bellek Hatası!");
    free(lst);
    exit(EXIT_FAILURE);
}
lst[0] = malloc(4 * sizeof(int));
lst[1] = malloc(6 * sizeof(char));
if (lst[0] == NULL or lst[1] == NULL) {
    printf("Bellek Hatası!\n");
    free(lst[0]);
    free(lst[1]);
    exit(EXIT_FAILURE);
}

Ne güzel bir özelliği var bu void'in değil mi? Peki başka türlü nasıl yapardık? struct kullanamaz mıydık? Kullanırdık elbette.

Aşağıdaki kodlara bakalım:

#include <stdlib.h>
#include <stdio.h>

typedef struct class_Lst {
    int *arr;
    char *str;
} class_Lst;

class_Lst *Lst(int *arr, char *str) {
    class_Lst *self = malloc(sizeof(class_Lst));
    if (self == NULL) {
        printf("Bellek Hatası!\n");
        free(self);
        exit(EXIT_FAILURE);
    }
    self -> arr = arr;
    self -> str = str;
    return self;
}

int main() {
    class_Lst *lst = Lst((int[]){1, 2, 3, 4}, "hello");
    printf("%d, %s\n", lst -> arr[0], lst -> str);
    free(lst);
    return 0;
}

Buradaki -> ifadesi, lst.arr[0] ifadesindeki . sembolüne karşılık gelir ve pointer struct için kullanılır. Yani bir C kodunda ne zaman -> işaretini görsek, bunun Python’daki . işareti ile aynı anlama geldiğini anlamamız gerekir ve bu sembolün kullanımı sadece yapıyı (struct’u) işaret eden pointerlar için geçerlidir. Eğer başka bir amaçla da kullanılıyorsa, lütfen haberdar edin.

Bu arada, bilgisine güvendiğim, bu forumu da okuyan bir arkadaşım, malloc'un kullanımı hakkında bir hatırlatmada bulundu. İlk mesajdaki örneklerime bakarsanız, hafızadan yer ayırırken, malloc’a tip ataması yaptım hep. İnternetteki kaynaklarda karşılaşabileceğiniz örneklerde malloc'a tip atandığını görebilirsiniz. Ama arkadaşımın söylediğine ve onun söylediğinden sonra yaptığım araştırmalara göre, bu yaklaşımın terkedilmesinin bir nedeni varmış. Şöyle anlatmaya çalışayım: C’nin doğumundan bugüne kadar, C diline ait kodları yorumlayıp çalıştırmak için bir sürü derleyici veya yorumlayıcı da yazıldı. Ve bu derleyiciler de zaman içerisinde evrim geçirdiler. Benim C kodu derlerken kullandığım derleyicinin adı gcc. Şu an Windows üzerindeki sürümü de 8.3.0. mingw-64 (minimal gnu) ortamına ait bir gcc sürümü, öyle ki, double tipteki verileri Windows’ta ekrana gerçek değerleriyle yansıtabilmek için, dosyanın başına #define __USE_MINGW_ANSI_STDIO 1 macrosu yazmak zorunda kalıyorsun. Şunu demeye çalışıyorum, bu derleyiciler ve dahi dillerin söz dizimi standartları zaman içerisinde değişiyorlar. En güncel örneğini Python’da da görebiliriz, Python’da bir sürüme kadar walrus operatörü denilen ifade kalıbı syntax error veren bir ifadeydi. İşte bu değişen standartlardan ötürü artık derleyiciler de hataların nereden çıktığını açıkça gösterebilecek seviyeye geldiler. Eskiden işler böyle değilmiş. Mesela bir pointer’a tip atamasını açıkça yapmak, yanlışlıkla stdlib.h yazmayı unuttuğumuzda, Undefined Behaviour oluşturan bir davranışa neden oluyormuş. Şimdi stdlib.h'ı include etmeden malloc kullanmaya çalışınca, gcc bize zaten “stdlib.h’ı include etmeyi unuttun galiba.” anlamına gelen bir mesajla karşılık veriyor. Dolayısıyla arkadaşıma bu UndefinedBehaviour davranışını gözlemlemek istediğimi söyledim. Ama işte sıkıntı şu ki, nereden bulacaksın UndefinedBehaviour davranışının oluşmasını sağlayan derleyiciyi.

Neyse arkadaşlar, şimdilik anlatacaklarım bu kadar. Bu mesajda Python’a biraz daha az değindik. Benim eğitimim zaten devam ediyor, paylaşmaya değer birşeyler bulursam, vaktim de olursa, seve seve paylaşırım zaten.

Kendinize iyi bakın. Görüşmek üzere.

4 Beğeni
#include <stdio.h>

int main(void)
{
	int i = 42;
	double f = 42.0;

	void *arr1[] = { &i, &f };
	printf("%i %f\n", *((int *) arr1[0]), *((double *) arr1[1]));

	union idvar {
		int ival;
		double dval;
	} arr2[] = { { .ival = i }, { .dval = f } };

	printf("%i %f\n", arr2[0].ival, arr2[1].dval);

	return 0;
}

Python objeleri de kocaman birer variant aslinda.

!

free(lst) eksik :smiley:

Neden bu isleri C’de yapmanin zor olduguna da guzel bir ornek oldugu icin dikkat cektim.

Fonksiyon donus turu olarak unit turune denk geliyor. (Tek olasi deger, sifir bilgi; hic bir sey dondurmemeye esdeger)
Fonksiyon parametre listesi olarak bos listeye denk geliyor. (C++ ve diger dillerdeki int hede() gibi.)
Point edilen deger turu olarak biraz bottom (⊥, Void) turu (sifir olasi deger, yaratilamaz, dondurulemez) biraz da char’a denk geliyor (1 byte eleman boyu, veri hafizasindaki her seye isaret edebilir.)

2 Beğeni
int **lst = malloc(2 * sizeof(int*);
if (lst == NULL) {
    printf("Bellek Hatası!\n");
    free(lst);
    exit(EXIT_FAILURE);
}
lst[0] = malloc(4 * sizeof(int));
lst[1] = malloc(sizeof(int));
if (lst[0] == NULL || lst[1] == NULL) {
    printf("Bellek Hatası!\n");
    free(lst[0]);
    free(lst[1]);
    exit(EXIT_FAILURE);
}
lst[0][1] = 1;
*lst[1] = 2;

Benim demeye çalıştığım şey şuydu, lst[1]'i de bir pointer olarak yazıyoruz. Python’daki gibi, [[1, 2, 3], 1] listesindeki 1, tek başına bir integer değer olarak, [1, 2, 3]'ü işaret eden bir pointer’ın yanında duramaz. Daha doğrusu bu integer’in yerine, onu işaret etmesi için başka bir pointer kullanmak gerekiyor.

Yani senin örneğinde de aynısı geçerli.

    int x[] = {1, 2, 3};
    double y = 42.0;

    void *arr[] = { x, &y }; // x zaten pointer, y'nin de adresini yerleştiriyoruz, kendisini değil.
    int *px = (int *)arr[0];
    double *py = (double *)arr[1];
    
    printf("%d, %.1f\n", *px, *py);
	return 0;

Dolayısıyla, Python’daki [[1, 2, 3], 1] veri yapısına benzer bir yapıyı sadece pointerları kullanarak yukardaki gibi oluşturmaya çalıştığımızda, [[1, 2, 3], [1]] gibi bir veri yapısı elde ederiz. (Gözlemim bu yönde oldu.)

Uzun lafın kısası, Python’daki arrayimize benzer bir array’i, pointer kullanarak, ancak aşağıdaki ifadeye benzer ifadelerle oluşturabiliriz:

void *arr[] = { (int[]){1, 2, 3}, (int[]){1} };

Aşağıdaki ifade ise hata oluşturur.

void *arr[] = { (int[]){1, 2, 3}, 1 };

typo error yapmışım. onun yerine = yazmam lazımdı :smiley: Belki de, kodu önce başta NULL değilse devam et gibi yazmak isteyip, sonra da vazgeçmiş ve o kısmı da öyle bırakmış olabilirim.

O da yukarda :face_with_hand_over_mouth: Güncelledim.

1 Beğeni