Yusuf Bülbül

An Engineer

C Dilini Nesne Yönelimli Kullanmak

for english click

Mühendisliği bir çok farklı şekilde tanımlayabilirsiniz. Fakat ben tanımlayacak olsaydım; yaptığınız işi şematiğe dökebiliyor ve üzerinde parametrik olarak verimlilik ya da tasarım anlamında ayarlamalar yapabiliyorsanız, mühendislik yapıyorsunuzdur diyebilirdim.  Bir konuda usta bile olsanız mühendisliğini yapmak bu yüzden farklı bir durumdur.

Yazılım dünyasının bu günlere gelmesinin en önemli sebebi tabi ki nesne yönelimli yazılımın geliştirilmiş olmasıdır. Çünkü nesne yönelimli bir tasarım, kod üzerinde mühendislik yapabilmenizi kolaylaştırıyor. Bunun farkına vardığınızda uzman yazılımcı rütbesine yükseliyorsunuz.  Neden diye soracak olursanız, bu ayrımı yapabilmek için biri iyi diğeri kötü kod tasarımına sahip iki büyük projede yer almış olmanız gerekiyor. Basit işler yapan maksimum beş-on bin satır koddan bahsetmiyorum. Burada belki yüz binlerce satır koddan bahsediyorum.  Böyle büyüklükte bir projede, kendinizi kod yığını içerisinde boğulurken bulduğunuzda nesne yönelimli mantığın ve yazılım tasarım desenlerinin kıymetini anlıyorsunuz.

Bu gün gömülü sistemler ve çekirdek programlar halen öncelikli olarak C dilini kullanıyor.  Ayriyeten, unix sistemler C dilinde yazılmış olduğundan ve popüler bir çok dilin tabanını oluşturduğundan, C dilinin unutulması ve ya daha geri planda kalması gibi bir durum söz konusu değil şimdilik. Fakat C dili yapısal olarak nesne yönelimli olmadığından özellikle büyük projelerde, kod tasarımı açısından dezavantajlara sahip bir dil.  Bu yüzden C++ kullanabiliyorsak, C yerine C++ kullanmak oldukça mantıklı. Fakat çoğu gömülü platformun halen C++ desteği yok. Bu durumda  C dili nesne yönelimli olmasa da bu dili nesne yönelimli dillere benzeterek kulllanabiliriz. Bu da sizin kodu daha anlamlı bir şekilde şematize edebilmenize ve tasarlaya bilmeye yarıyor tabi ki. Bu yazımda C dilini de C++ gibi nesne yönelimli bir şekilde kullanabilmeyi ele alacağım.

Öncelikle C de basit bir nesne tanımlama ile başlayalım.  Örneğin C++ da şöyle bir nesne olsun;

person.h dosyası;

class Person 
{

Public:

    Person();
    ~Person();
    
    std::string name;
    virtual void talk(int Opinion);
    

Private:
    
    const int own_opinion = 1;
    void think(int opinion);

};

person.cpp dosyası;

#include "person.h"

//private function
void Person::think(int Opinion)
{
  if(own_opinion != Opinion)
    std::cout << "person " << name << "disagree" << std::endl;
  else
    std::cout << "person " << name << "agree" << std::endl;

}
    

//public function
void Person::talk(int Opinion)
{
   think(Opinion);
   std::cout << "person " << name << "talking" << endl;
}


Nesenin kullanımı;

#include "person.h"

int main()
{   
    Person person1;
    Person person2;    

    person1.name = "Alice";
    person2.name = "Bob";

    person1.talk(1);
    person2.talk(2);

    return 0;

}

Şimdi C++ ve nesne yönelimli programlama bildiğinizi düşünerek bu nesneyi bir de C dilinde oluşturmaya çalışalım.

person.h dosyası şu şekilde olacak;

#define __person_H
#ifdef __person_H

struct Person
{
    const char *name;   //public variable
    void (*talk)(void *Person, int Opinion); //public function
};

static void think(struct Person *Person, int Opinion); //private
static void talk(void *Person, int Opinion);  //public

// constructer and deconstructer
void initPerson(struct Person *Person);
void deinitPerson(struct Person *Person);

#endif

person.c dosyası şu şekilde olacak;

#include "person.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

//private variable defination
static const int own_opinion = 1;



//private function
static void think(struct Person *Person, int Opinion)
{
    if(own_opinion == Opinion)
       printf("Person %s agree\n", Person->name);
    else
       printf("Person %s disagree\n", Person->name);
}



//public function defination
static void talk(void *Person, int Opinion)
{
    struct Person *temp = (struct Person *) Person;
    think(Person, Opinion);
    printf("Person %s talking...\n", temp->name);
}



// constructer and deconstructer
void initPerson(struct Person *Person)
{
    Person->talk = talk;          //define where is the public function in object
}



void deinitPerson(struct Person *Person)  //Deconstructer
{
}

 

C dilinde yukarıdaki nesnenin kullanımı şu şekilde;

#include <stdio.h>
#include "person.h"

int main()
{
    struct Person person1;
    struct Person person2;

    initPerson(&person1);
    initPerson(&person2);

    person1.name = "Alice";
    person2.name = "Bob";

    person1.talk(&person1, 1);
    person2.talk(&person2, 2);


    deinitPerson(&person1);
    deinitPerson(&person2);

    return 0;
}

Çıktı;

Person Alice agree
Person Alice talking...
Person Bob disagree
Person Bob talking...

“Person” isimli nesnenin genel(public) fonksiyonlarını, yapının(struct) içerisinde  fonksiyon göstericisi(function pointer) olarak tanımlıyoruz. Bu fonksiyonlar, aynı zamanda “.h” ve “.c” uzantılı dosyalarda, statik olarak tanımlanması gerekiyor. Çünkü C dilinde statik olarak tanımlanan fonksiyon ve değişkenlere dosya dışından erişilemiyor. Genel fonksiyonlara tek erişimin nesne işaretleticisi(Object Pointer) üzerinden olması gerekiyor.  Bu yüzden bunları yapının(struct) içerisine koyuyoruz. Fakat özel(private) fonksiyon ve değişkenlere nesne üzerinden de ulaşılamaması gerekiyor.  Bunlar sadece o nesnenin bulunduğu dosyadan ya da nesne içinden erişile bilmeli. Bu yüzden bu fonksiyon ve değişkenler yapının(struct) içerisine konmayıp sadece “.c” uzantılı dosyada statik olarak tanımlanıyor.  Statik fonksiyonları özellikle C de oldukça fazla görürüz.  Çünkü, özel(private) fonksiyon ve değişkenler için “.c” dosyasındaki statik tanımlaması biçilmiş kaftandır.  Bu yüzden nesne içerisinde tanımladığımız her şeyi, statik olarak tanımlıyoruz. Bu tanımlamanın dışında kalan tek fonksiyonlar nesnenin yapıcı ve yıkıcı fonksiyonları olarak kalıyor.

Burada iki seçenek mevcut.  Nesne yapıcı fonksiyona geçirilmeden önce, heap de ve ya stack de tanımlanabilir. Ben Stack’ de tanımladım. Daha sonra, nesnenin yapıcı fonksiyonunda, genel(public) fonksiyonların nerede olduğunu belirtmek gerekiyor. Aynı zamanda bütün üye fonksiyonların(member function) ait olduğu nesnenin işaretleyicisi(pointer) parametre olarak alması gerekiyor. Çünkü bu fonksiyon içerisinden nesnenin diğer üye fonksiyon ve değişkenlerine ulaşılabilmesi gerekiyor.

Nesneyi bu şekilde tanımladık. Peki bu yeterli mi? Tabi ki yeterli olabilmesi için kalıtım işlemi denilen ebeveyn ve çocuk nesneleri de tanımlayabilmek gerekiyor.  Bunu da C de kendimiz yapabiliriz. Örneğin yukarıdaki “person” fonksiyonunu ebeveyn nesne olarak alan bir “student” isimli nesne oluşturalım.

Bu işlem C++ kullanırken oldukça kolay.  C++’da şu şekilde tanımlanabilir;

class Student : public Person
{

private:
    void cheat(int Lesson)
    {
        std::cout << "Student "<< name << "cheating on lesson" << Lesson << std::endl;
    }
    std::string favorite_lesson;

public:
    Student();
    ~Student();

    std::string degree;
    void study(int Lesson)
    {
        if(Lesson != favorite_lesson)
            cheat(Lesson);
        else
            std::cout << "Student "<< name << "studying on lesson" << Lesson << std::endl;

    }

    void talk(int Opinion)
    {
        std::cout << name << "talking at " << degree << std::endl;

    }

};

 

Şimdi bu programı C de yazalım.

student.h dosyası şu şekilde olmalı;

#define __student_H
#ifdef __student_H

#include "person.h"

struct Student
{
    struct Person person;

    const char *degree;
    void (*study)(struct Student *Student, int Lesson);
};


static void talk(void *Student, int Opinion);
static void study(struct Student *Student, int Lesson);
static void cheat(struct Student *Student, int Lesson);

void initStudent(struct Student *Student);
void deinitStudent(struct Student *Student);



#endif

student.c dosyası şöyle olmalı;

#include "student.h"
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>

const int fovarite_lesson = 1;

static void talk(void *Student, int Opinion)
{
    struct Student *student = (struct Student *)Student;
    printf("%s talking at %s", student->person.name, student->degree);
}



static void cheat(struct Student *Student, int Lesson)
{
    printf("Student %s cheating on lesson %d...\n", Student->person.name, Lesson);
}



static void study(struct Student *Student, int Lesson)
{
    if(Lesson != fovarite_lesson)
        cheat(Student, Lesson);
    else
        printf("Student %s studying on lesson %d...\n", Student->person.name, Lesson);


}



void initStudent(struct Student *Student)
{
    initPerson(&Student->person);
    Student->study = study;
    Student->person.talk = talk;
}


void deinitStudent(struct Student *Student)
{
    deinitPerson(&Student->person);
}

çıktı;

Student Alice cheating on lesson 3...
Alice talking at High School

Böylelikle  C dilinde de yazılım tasarım desenleri uygulanabiliyor.  Bu da kaliteli kod ve dolayısıyla kaliteli mühendislik getiriyor. Yukarıdaki kodlar ve tasarımlar kendimin yazmış olduğu ve bir çok yerden farklı tanımlamalar görüp bunları harmanlayarak oluşturduğum C de nesne tasarımları. Bu kodlar “gcc (Debian 7.2.0-19) 7.2.0” kullanılarak derlenip “Kali GNU/Linux Rolling 64-bit” sistemde test edilmiştir.

 

 

 

 

6 Yorum Mevcut

  1. merhabalar. yazılarınız çok faydalı. Başarılarınızın devamını dilerim. Bu kısmı anlamak için çalışırken şuraya kafam takıldı:

    “person.c dosyası şu şekilde olacak;”
    başlığı altındaki kodlarda 26.satırda bir yanlışlık olabilir mi?
    25- struct Person *temp = (struct Person *) Person;
    26- think(Person, Opinion); //parametre olarak “Person” değil de, “temp” verilmesi gerekmez miydi?
    27- printf(“Person %s talking…\n”, temp->name);

  2. Hocam kodları aynı şekilde yazdım. visual studio19 da derledim. Ama böyle bir hata verdi. Araştırdım fakat ne olduğunu bulamadım. Ne olabilir ?

    1>—— Derleme başladı: Proje: yusufBulbul_orn_C_kismi, Yapılandırma: Debug Win32 ——
    1>person.cpp
    1>yusufBulbul_orn_C_kismi.c
    1>yusufBulbul_orn_C_kismi.obj : error LNK2019: Çözümlenmemiş dış sembol _initPerson, _main işlevinde başvurdu
    1> Tanımlanan ve eşleşme olasılığı olan sembollerle ilgili ipucu:
    1> “void __cdecl initPerson(struct Person *)” (?initPerson@@YAXPAUPerson@@@Z)
    1>yusufBulbul_orn_C_kismi.obj : error LNK2019: Çözümlenmemiş dış sembol _deinitPerson, _main işlevinde başvurdu
    1> Tanımlanan ve eşleşme olasılığı olan sembollerle ilgili ipucu:
    1> “void __cdecl deinitPerson(struct Person *)” (?deinitPerson@@YAXPAUPerson@@@Z)
    1>C:\Users\Cemal\source\repos\yusufBulbul_orn_C_kismi\Debug\yusufBulbul_orn_C_kismi.exe : fatal error LNK1120: 2 çözümlenmemiş dışlar
    1>”yusufBulbul_orn_C_kismi.vcxproj” projesini oluşturma tamamlandı — BAŞARISIZ OLDU.
    ========== Oluşturma: 0 başarılı, 1 başarısız, 0 güncel, 0 atlandı ==========

    1. Sorun, dosya isminin “person.c” olması gerekirken “person.cpp” olmasından kaynaklanıyormuş. Şimdi farkettim. Değiştirince düzeldi.

  3. Hocam;
    static void think(struct Person *Person, int Opinion); //private
    static void talk(void *Person, int Opinion); //public

    -Burada, talk() fonksiyonunu private değil de public yapan şey, struct içinde tanımladığımız fonksiyon pointeri’nın, constructer çağırıldığı zaman talk() fonksiyonuna işaret etmesi mi?

    -ikinci sorum da;
    static void think(struct Person *Person, int Opinion); //private
    static void talk(void *Person, int Opinion); //public

    burada talk() fonksiyonu parametre olarak neden “void *Person” alıyor? think() te olduğu gibi “struct Person *Person” parametre alsaydı olmaz mıydı ?

  4. Merhaba Cemal Bey,

    Yorumlarınız için teşekkür ederim. Sırayla sorularınızı yanıtlamaya çalışacağım;

    1-) Birinci sorunuzda haklısınız. person.c 26. satırda Person değişkeni yerine temp değişkeni fonksiyona verilebilirdi. Fakat yukarıdaki örnekteki gibi verilse bile fonksiyon parametresi Void* olduğundan otomatik olarak derleyici, o parametreyi herhangi bir tipten Void* tipine “cast” edecektir yani dönüştürecektir. Bu durumda bir problem olmayacaktır.

    2-) Yukarıdaki kodları ben gcc-8 kullanarak Kali işletim sisteminde derledim. Bazen kullandığınız derleyiciye göre bazı sıkıntılar olabiliyor. Visual studio genellikle MVSC derleyicisi kullanıyor. Kullandığınız derleyici ve IDE yi İngilizce arayüzde kullanmanızı tavsiye ederim. Çünkü karşılaştığınız bu gibi problemlerde internetten aratarak daha fazla çözüm ve teknik detay bulabilirsiniz.

    Gelelim derleme problemine. Burada derleyici derleme işleminin Linker aşamasında hata vermiş ve initPerson ismindeki fonksiyonu bulamadığını söylüyor. Bu durumda person.h dosyasını hata verilen dosyaya dahil ettiğinize ve initPerson() fonksiyonunun person.h dosyasında prototipinin ve percon.c dosyasında da kendisinin gerçekten bulunup bulunmadığına emin olun.

    3-) Bir fonksiyonu public yapan şey, fonksiyona nesne dışından ve nesne referansı üzerinden erişimin sağlanabilmesidir. private yapan şey ise, o fonksiyona nesne dışından hiç bir şekilde erişile memesidir. Yani haklısınız. Yukarıdaki örnekte, private fonksiyonlar .c dosyasında static olarak tanımlandığından o dosya dışından erişilemez durumdadır. Bu yüzden private gibi davranırlar. Public fonksiyonları ise sınıfın struct’ı içerisine tanımladığımızdan ve fonksiyonların işaretleyicilerini(Pointer) verdiğimizden herhangi bir yerden “struct” referansı ile bu fonksiyonlara erişilebilir.

    4-) talk fonksiyonunun void *Person değil de Person *Person parametresi alması bir seçenektir. Tabi ki öyle de olabilir. Ancak, void* her tipdeki işaretçiye “Cast” edilebildiği için “Polimorfism” uygulanması gereken durumlarda daha kullanışlı olabilir. Örneğin burada Student sınıfı, Person sınıfından türetilmiş olduğundan bu ikisinin birbirine dönüşmesi istenebilir bazı durumlarda. Bu yüzden dönüşüm adına void* kullanmak daha mantıklı olabilir. Fakat bu örnek için dediğinizde haklısınız. Siz söylediğiniz tarz bir kullanımı seçebilirsiniz.

    Umarım cevaplar yardımcı olur.

    Kolay Gelsin. 🙂

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir

Copyright © Tüm Hakları Saklıdır.