Forum: Diğer Konular RSS
C / C++ - Değişken Parametreler İle İlgili
İbrahim #1
Üye Eki 2015 tarihinden beri · 142 mesaj
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Konu adı: C / C++ - Değişken Parametreler İle İlgili
Selamün Aleyküm;

Kodum şu şekilde:
void foo(const char* str, ...)
{
  va_list args;
  va_start(args, str);
  for (int i = 0; i < 3; i++)
    cout << va_arg(args, char*) << endl;
  va_end(args);
}

Sual 1-) va_start ile değişken parametreler için ilk konum neresi olacak onu konumlandırıyoruz. Fakat bunun için en az bir tane parametre gerekiyor (bu kodda const char* str). Peki ben bu şekilde varsayılan bir parametre koymadan va_list args'ı konumlandıramaz mıyım?

Sual 2-) Kodda da görüldüğü gibi for ile 3'e kadar dönüyorum, çünkü önceden 3 tane argüman göndereceğimi biliyorum. Peki Kaç tane parametre gönderileceğini bilmediğim zamanlarda nasıl bu argümanları alabileceğim? Yani argüman sayısını nasıl bulabilirim?

Sual 3-) va_arg fonksiyonu ile argümanları yakalayabiliyoruz. Lakin va_arg'ın ikinci parametresini argümanın tipini bildiğim için char* yazdım. Lakin argüman tipini bilmediğim zamanlarda nasıl bir yol izlemeliyim? Aslında türleri decltype, std::is_same veya typeid gibi özelliklerle bulup ona göre işlem yaptırabilirim, fakat bu gibi ek denetimlere gerek kalmadan yapabilir miyiz?

Sual 4-) Aslında benim şu anda ihtiyacım olan bir konu da şu: void foo(int n, ...) gibi bir fonksiyonu şu şekilde kullanıyoruz foo(1, "a", "b", "c"); Peki ben bir diziyi bu fonksiyona değişken parametre olarak nasıl gönderebilirim?
Bir örnek olarak:
// foo Bildirimi:
void foo(int n, ...);
.
.
.
char* dizi[] = { "a", "b", "c" };
foo (12, dizi);
Neden böyle yapmak istediğimi merak ediyorsanız şu şekilde anlatayım: Bazı dillerde değişken parametreler olmadığı için veyahut da karmaşık yollardan yapıldığı için C++'da yazdığım fonksiyonu o dilde kullanırken dizi olarak atmak istiyorum. Niçin C++ fonksiyon bildiriminde parametreyi değişken parametreler yerine dizi kullanmıyorum? Çünkü verilen parametrelerin başka bir fonksiyona parametre olarak verilmesi gerektiğindendir. Mesela bir örnek:

// C++ Fonksiyonu:
void foo(int n, ...)
{
  va_list args;
  va_start(args, n);
  for (args içinde dön)
    bar(Argümanları bu fonksiyona parametre olarak geçir.);
   // ÖRNEK: bar (1, "a", "b", "c"); gibi
}
 
// Object Pascal Kodu:
// Bu dilde diziyi argüman olarak vermek en kolayı,
// çünkü C / C++ gibi değişken parametreleri dolaylı olmayan yoldan desteklemiyor. ::
 
foo(1, ['aa', 'bb', 'cc', 22, 33, 4.5]);
Yani ya C++ fonksiyonunda dizi olarak parametre tanımlayacağım, ya da değişken parametre tanımlayıp buna dizi geçirebileceğim. İlk yolu tercih edersem de örnek koddaki gibi dizi elemanlarını başka bir fonksiyona nasıl tek tek argüman olarak atayacağım? Her 2 yol da yapılabilir mi? Yapılırsa nasıl yapılır?

Teşekkürler!
Bu mesaj İbrahim tarafından değiştirildi; zaman: 2017-01-22, 05:11.
acehreli (Moderatör) #2
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4448 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
C++ ve D gibi dillere yönelmemizin nedenlerinden birisi C'nin bu gibi yetersizlikleridir.

2) Kaç adet (ve ne türden) parametre olduğunu ilk parametre bildiriyor.

1) O yüzden, en az bir parametre şart. :)

3) Parametre türlerini nasıl bileceğini sen belirlemek ve kullanıcıya güvenmek zorundasın. Örneğin, printf öyle yapar: %s görmüşse hiçbir sakıncası olmadan o adresten başlar ve \0 karakterini bulana kadar belleğe erişe erişe yazdırır. gcc gibi derleyicilere printf gibi işlevlerin aldıkları böyle düzen dizgilerini denetletebiliyoruz ama nasıl yapıldığını şimdi hatırlamıyorum.

4) C++'ta yine de C dizileri değil, std::vector kullanılmalı. Sonra nasıl olsa ilk elemanın adresini &v.front() ile alabiliriz. (C++11'de v.data() ile.)

bar() çağrısını ancak dinamik bir dili çağırıyorsan kullanabilirsin. Statik türlü bir dilde bar(1) mi yoksa bar(2, 3) mü yaptığının derleme zamanında bilinmesi ve kodun tam da onu çağırmak için oluşturulması gerekir. Eğer bir Object Pascal interpreter'ı çağıracaksan, herhalde bütün çağrı dizgisini "bar(2, 3)" diye oluşturmak gerekiyordur; ama dizgi olarak...

Bunun nasıl yapıldığını o dili çağırmaya olanak veren çatıdan öğrenmelisin. Örneğin, C'den Python'a dizilerin nasıl geçirildiğini kullandığın çatı tarafından belirlenmiştir.

Ali
İbrahim #3
Üye Eki 2015 tarihinden beri · 142 mesaj
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Teşekkürler Ali Hocam;

2) Kaç adet (ve ne türden) parametre olduğunu ilk parametre bildiriyor.
Hangisinden bahsediyorsunuz? Kaç parametre bulunuyor nasıl bulacağım? Ayrıca hep aynı tipte parametre vermek zorunda kalıyoruz. Yani foo(1, "string", 15, 3.2) gibi karışık tipte argüman atamıyoruz o zaman. Ama bu şekide karışık argüman atan kodlar gördüm. Zaten böyle olabilmesi gerekmez mi? Yoksa hep aynı tipte parametreler alan bir fonksiyonu her zaman yazmıyoruz, bir fonksiyon farklı tipte parametreler içerebiliyor.
Acaba şu şekilde yaparsam karışık tipte parametre atabilir miyim?:
// foo Bildirimi:
void foo(int sayi, ...);
 
...
...
 
class Types
{
private:
  union
  {
    const char* s;
    int i;
    long l;
    float f;
    double d;
    char c;
    bool b;
  };
public:
  Types();
  Types(const char* s) : s(s) {}
  Types(int i) : i(i) {}
  Types(long l) : l(l) {}
  Types(float f) : f(f) {}
  Types(double d) : d(d) {}
  Types(char c) : c(c) {}
  Types(bool b) : b(b) {}
};
 
// sonra şu şekilde olabilir mi?:
Types type = va_arg(args, Types);
...
...
foo(1, "Ali", "Veli", 123, 15.9, 7.7, 'c', true);
bar() çağrısını ancak dinamik bir dili çağırıyorsan kullanabilirsin. Statik türlü bir dilde bar(1) mi yoksa bar(2, 3) mü yaptığının derleme zamanında bilinmesi ve kodun tam da onu çağırmak için oluşturulması gerekir. Eğer bir Object Pascal interpreter'ı çağıracaksan, herhalde bütün çağrı dizgisini "bar(2, 3)" diye oluşturmak gerekiyordur; ama dizgi olarak...
JNI gibi C++ içinde Object Pascal çağırmıyorum :). Yaptığım sadece C++ fonksiyonlarını .so, .a veya .o dosyaları haline getirip Object Pascal'da kullanmak. Yani kodlarım bu uzantılarda zaten derlenmiş oluyor.
Yapmak istediğim sadece dizi şeklinde parametreleri alıp, bunları C++ tarafında bir_fonksiyon("foo", d[0], d[1], d[2], ......) gibi atamak istiyorum. Hatta Object Pascal'ı iptal edin. Aynı şeyi C++ ile yaptığımızı düşünün. Misal olarak:
// Tamamen C++ kodu:
void bir_fonksiyon(const char* str, ...)
{
  va_list args;
  ....
  baska_bir_fonksiyon(Tüm Değişken Parametreleri Bu Fonksiyona Argüman Olarak Atayabilir miyim?);
  // baska_bir_fonksiyon(x, y, z); gibi
  ....
  va_end(args);
}
 
// Yani Dizi de olsa aynısı oluyor:
 
void bir_fonksiyon(Types args[])
{
  ...
  baska_bir_fonksiyon(args dizisinin elemalarını tek tek buna argüman olarak atayabilir miyim?);
  // Yani sonuç şöyle olmalı:
  // Programcı bir_fonksiyon(std::initializer_list<Types>({ "Ali", "Veli", 123, 1.7, 'c', true }));
  // şeklinde kullandığında sonuç şöyle olmalı:
  // baska_bir_fonksiyon("Ali", "Veli", 123, 1.7, 'c', true);
  ...
}
acehreli (Moderatör) #4
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4448 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
İbrahim:
Kaç parametre bulunuyor nasıl bulacağım?

Bu konuda derleyicinin, C'nin, ve C++'ın sana bir yardımı yok. (Aslında artık doğru değil: Yeni C++'ta variadic template olanağı var.)

Önce sıradan bir işleve bakalım: Bildirimi foo(int, double) gibi olan bir işlev çağrıldığında çağıran taraf çağrı yığıtına bir int ve double yerleştiriyor ve çağrıyı alan taraf da bir int ve double okuyor.

Hatırlarsak, iki taraftaki derleyiciler farklı olabilir ve normalde ellerinde işlevin bildiriminden başka hiçbir şey yoko. Yani, bildirimin söylediği int ve double parametreleri doğrularlar. Yoksa, derleme hatası alırız.

Belirsiz sayıda parametreli durumlarda işlevlerde ise iki taraf arasında hiçbir sözleşme yok. Çağıran taraf istediğini doldurur ve derleyici hiçbir şey diyemez. Çağrıyı alan tarafta da derleyici hiçbir şey söyleyemez çünkü parametreleri va_list marifetiyle çeken sensin.

Peki sen ne kadar çekeceğini nereden bileceksin? printf gibi bir düzenek kullanarak: printf'e "%d%s" gibi düzen dizgileri vermemizin nedeni o. printf, bize güvenerek bir int ve bir dizgi kullanır.

O yüzden, tek veya daha fazla parametre gerektirerek çağıranın sana ne yerleştirdiğini tarif etmesini istemek ve ona güvenmek zorundasın.

Hatırlatmak amacıyla, böyle işlevlere yalnızca temel türden parametre gönderebiliyoruz. std::string bile gidemez.

Ayrıca hep aynı tipte parametre vermek zorunda kalıyoruz. Yani foo(1, "string", 15, 3.2) gibi karışık tipte argüman atamıyoruz o zaman.

İşte, printf gibi davranarak bilebiriz.

class Types
{
    // ...
};

Öyle de olur. Temelde C++17'nin std::any'sini düşünüyorsun. Ama aynı sorun yine var: kullanan taraf asıl türü nasıl bilecek?

Object Pascal'da kullanmak.

Güzel. Herhange extern"C" yapmak zorundasın zaten. Object Pascal'ın sana türleri tarif etmesi ve senin buna güvenmen gerek. Object Pascal'da variadic parametre var mı? Olsa, tarif olayını o taraftaki bir katmanla halledebilirsin. Senin yazacağın o katman C++ işlevine ek bir parametre ile çağırır: foo("int,double", i, d) böylece tam bilebilirsin ama galiba normal Object Pascal'da variadic parametre yokmuş ama extension'lar olabilirmiş. (?)

Ali
İbrahim #5
Üye Eki 2015 tarihinden beri · 142 mesaj
Grup üyelikleri: Üyeler
Profili göster · Bu konuya bağlantı
Çok teşekkürler Ali Hocam;

Yeni C++'ta variadic template olanağı var.
İşte bu çok iyi, öyle değil mi? C++11 ile kullanılabiliyor. Bunu C türü değişken parametreler yerine kullansam sizce nasıl olur?

O yüzden, tek veya daha fazla parametre gerektirerek çağıranın sana ne yerleştirdiğini tarif etmesini istemek ve ona güvenmek zorundasın.

Hatırlatmak amacıyla, böyle işlevlere yalnızca temel türden parametre gönderebiliyoruz. std::string bile gidemez.
Fakat QtAndroidExtras'da şöyle bir kod yazabiliyoruz:
enum Duration {
    SHORT = 0,
    LONG = 1
};
 
QtAndroid::runOnAndroidThread([message, duration] {
        QAndroidJniObject javaString = QAndroidJniObject::fromString(message);
        QAndroidJniObject toast = QAndroidJniObject::callStaticObjectMethod("android/widget/Toast", "makeText",
                                                                            "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;",
                                                                            QtAndroid::androidActivity().object(), // jobject türü
                                                                            javaString.object(), // jobject türü
                                                                            jint(duration)); // jint türü
        toast.callMethod<void>("show");
    });
Burada değişken parametreler olarak QtAndroid::androidActivity().object(), javaString.object() ve jint(duration) kullanılmış. Yani 2 jobject türü, 1 tane de jint türü var. Yani farklı türler ve ilkel türler değil (bool, char vs. gibi).

Tarif edilmesi olayı da sanırım bu kod için signature (imza) ile oluyor. Yani adamlar şu argümanı "(Landroid/content/Context;Ljava/lang/CharSequence;I)Landroid/widget/Toast;" boşuna koymamışlar. Demek ki arkaplanda parametre türlerini bu şekilde biliyorlar. Mesela (Ljava/lang/String;IZ)I gibi bir imzadan geriye int döndüren ve Java string, int ve boolean türlerinde parametresi olan bir metot kastedildiğini anlıyorlar.

QtAndroidJniObject::callStaticObjectMethod üye fonksiyonu arkaplanda QJNIObjectPrivate::callStaticObjectMethod üye fonksiyonunu çağırıyor:
QJNIObjectPrivate QJNIObjectPrivate::callStaticObjectMethod(const char *className,
                                                            const char *methodName,
                                                            const char *sig,
                                                            ...)
{
    va_list args;
    va_start(args, sig);
    QJNIObjectPrivate res = callStaticObjectMethodV(className, methodName, sig, args);
    va_end(args);
    return res;
}
callStaticObjectMethodV bu üye fonksiyon da şu şekilde:
QJNIObjectPrivate QJNIObjectPrivate::callStaticObjectMethodV(const char *className,
                                                             const char *methodName,
                                                             const char *sig,
                                                             va_list args)
{
    QJNIEnvironmentPrivate env;
    jobject res = 0;
    jclass clazz = loadClass(className, env);
    if (clazz) {
        jmethodID id = getCachedMethodID(env, clazz, toBinaryEncClassName(className), methodName, sig, true);
        if (id) {
            res = env->CallStaticObjectMethodV(clazz, id, args);
            if (res && env->ExceptionCheck())
                res = 0;
        }
    }
 
    QJNIObjectPrivate obj(res);
    env->DeleteLocalRef(res);
    return obj;
}

Yani benim anladığım kadarıyla bu böyle jni.h başlık dosyasında tanımlı olan callStaticObjectMethodV fonksiyonunu çağırana kadar gidiyor. Yani aslında Qt da bu değişken parametrelerin nasıl kullanılacağını jni.h başlık dosyasına bırakmış :).

Object Pascal'da variadic parametre var mı?
Aslında var, şurada bir örnek var: http://stackoverflow.com/a/33180889 varargs anahtar sözcüğü ile yapılabiliyor.
Peki C / C++'da dizi elemanlarını teker teker bir fonksiyona argüman olarak geçirme işlemini C / C++'da herhangi bir şekilde yapamaz mıyız? Yani bunun hiçbir yolu yok mu? Ha, olmadı, ben de varargs ile Object Pascal'da kullanırım :).
acehreli (Moderatör) #6
Kullanıcı başlığı: Ali Çehreli
Üye Haz 2009 tarihinden beri · 4448 mesaj
Grup üyelikleri: Genel Moderatörler, Üyeler
Profili göster · Bu konuya bağlantı
İbrahim:
QtAndroid::androidActivity().object(), javaString.object() ve jint(duration) kullanılmış.

Nesne yönelimli programlamaya girinci işler değişir çünkü nesnenin kendisine türünü sorabilirsin. Belki onlar da öyle yapıyorlardır... diyecektim... ama herhalde işlevin ne parametre aldığını 'signature' belirliyordur. Ona yanlış 'signature' vermeyi dene bakalım neler olacak. ;) (Tamam, bunu sen de aşağıda yazmışsın.)

Yani 2 jobject türü, 1 tane de jint türü var. Yani farklı türler ve ilkel türler değil (bool, char vs. gibi).

C++ standardı bunun desteklenmediğini söyler. Belki de jint, vs. perde arkasında bir nesne göstergesidir.

Peki C / C++'da dizi elemanlarını teker teker bir fonksiyona argüman olarak geçirme işlemini C / C++'da herhangi bir şekilde yapamaz mıyız? Yani bunun hiçbir yolu yok mu? Ha, olmadı, ben de varargs ile Object Pascal'da kullanırım :).

Ben bilmiyorum ama bazı cambazlıklarla halledilebilebilir:

 

Sonuçta, variadic parametre değerleri çağrı yığıtında nasıl duruyorlarsa sen de aynısını oluşturup işlevi çağırabilirsin. (Ben araştırmadan yapamam. :) )

Ali
Doğrulama Kodu: VeriCode Lütfen resimde gördüğünüz doğrulama kodunu girin:
İfadeler: :-) ;-) :-D :-p :blush: :cool: :rolleyes: :huh: :-/ <_< :-( :'( :#: :scared: 8-( :nuts: :-O
Özel Karakterler:
Bağlı değilsiniz. · Şifremi unuttum · ÜYELİK
This board is powered by the Unclassified NewsBoard software, 20100516-dev, © 2003-10 by Yves Goergen
Şu an: 2017-08-16, 20:45:09 (UTC -07:00)