Nesneleri C ++ 'da işlevlere nasıl geçirebilirim?


249

C ++ programlama konusunda yeniyim ancak Java konusunda deneyimim var. C ++ işlevlerine nesneleri geçirmek için nasıl rehberlik gerekir.

İşaretçileri, referansları veya işaretçi olmayan ve referans olmayan değerleri iletmem gerekir mi? Java'da sadece nesnelere referans tutan değişkeni geçtiğimiz için böyle bir sorun olmadığını hatırlıyorum.

Bu seçeneklerin her birinin nerede kullanılacağını da açıklamak harika olurdu.


6
Hangi kitaptan C ++ öğreniyorsun?

17
Bu kitap şiddetle tavsiye edilmiyor . Stan Lippman'dan C ++ Primer'a gidin.
Prasoon Saurav

23
Senin sorunun var. Schildt temelde cr * p - Koenig & Moo tarafından Hızlandırılmış C ++ olsun.

9
Bjarne Stroustrup'un C ++ Programlama Dili'nden nasıl bahsetmediğini merak ediyorum. Bjarne Stroustrup, C ++ 'ın yaratıcısıdır. C ++ öğrenmek için gerçekten iyi bir kitap.
George

15
@George: TC ++ PL yeni başlayanlar için değil, ancak C ++ için İncil olarak kabul edilir . XD
Prasoon Saurav

Yanıtlar:


277

C ++ 11 için başparmak kuralları :

Geçiş değeri , zaman hariç

  1. nesnenin sahipliğine ihtiyacınız yoktur ve basit bir takma ad yapılır, bu durumda referansla geçersinizconst ,
  2. nesneyi değiştirmelisiniz, bu durumda değer değeri olmayan bir constbaşvuru ile geçiş kullanın ,
  3. türetilmiş sınıfların nesnelerini temel sınıflar olarak iletirsiniz, bu durumda başvuru ile geçmeniz gerekir . ( constBaşvuru ile geçilip geçilmeyeceğini belirlemek için önceki kuralları kullanın .)

İşaretçi ile geçiş neredeyse hiç tavsiye edilmez. İsteğe bağlı parametreler en iyi şekilde std::optional( boost::optionaldaha eski std libs için) olarak ifade edilir ve diğer adlandırma referans olarak iyi yapılır.

C ++ 11'in hareket semantiği, karmaşık nesneler için bile değere göre geçiş ve dönüşü çok daha çekici hale getirir.


C ++ 03 için başparmak kuralları :

Argümanlar geçmek ile constreferans zaman hariç

  1. bunlar işlevin içinde değiştirilmeli ve bu tür değişiklikler dışarıya yansıtılmalıdır, bu durumda referanssız olarak geçersinizconst
  2. işlev herhangi bir argüman olmadan çağrılabilir olmalıdır, bu durumda işaretçi ile geçebilirsiniz, böylece kullanıcılar NULL/ 0/ nullptryerine geçebilir ; bir işaretçiye bir bağımsız değişkene geçirilip geçirilmeyeceğinizi belirlemek için önceki kuralı uygulayınconst
  3. bunlar kopya ile geçirilebilen yerleşik türlerden
  4. onlar işlevi içinde değiştirilmesi ve bu tür değişiklikler gerekir değil sen hangi durumda dışında, yansıtılacaktır kopya geçmek (alternatif önceki kurallara göre geçmek ve fonksiyonun bir kopyası içini yapmak olacaktır)

(burada, "değere göre geç" olarak adlandırılır, "değere göre geç" denir, çünkü değere göre geçmek her zaman C ++ 03'te bir kopya oluşturur)


Bundan daha fazlası var, ancak bu birkaç yeni başlayanın kuralları sizi oldukça uzağa götürecek.


17
+1 - Bazılarının (Google) işlev içinde değiştirilecek nesnelerin sabit olmayan bir referans yerine bir işaretçi aracılığıyla geçirilmesi gerektiğini düşündüğünü de belirtmek isterim. Bunun nedeni, bir nesnenin adresi bir işleve iletildiğinde, söz konusu işlevin onu değiştirebileceği daha açıktır. Örnek: Referanslarla çağrı foo (bar) şeklindedir; referansın const olup olmadığı, bir işaretçi ile foo (& bar); ve foonun değişebilir bir nesneden geçtiği daha açıktır.
RC.

19
@RC İşaretçinin sabit olup olmadığını hala söylemez. Google'ın yönergeleri, çeşitli C ++ çevrimiçi topluluklarında çok haklı olarak ortaya çıktı - haklı olarak IMHO.

14
Diğer bağlamlarda google yol gösterebilirken, C ++ 'da stil rehberi gerçekten iyi değil.
David Rodríguez - dribeas

4
@ArunSaha: Saf bir stil rehberi olan Stroustrup, bir havacılık şirketi için geliştirilmiş bir kılavuza sahiptir. Google kılavuzuna göz attım ve birkaç nedenden dolayı beğenmedim. Sutter & Alexandrescu C ++ Kodlama Standartları okumak için harika bir kitaptır ve birkaç iyi tavsiye alabilirsiniz, ancak gerçekten bir stil kılavuzu değildir . İnsanlar ve sağduyu dışında, stil için otomatik bir denetleyici bilmiyorum .
David Rodríguez - dribeas

3
@anon Ancak, bir argüman bir işaretçi üzerinden geçirilmezse, değiştirilmediğinin garantisini alırsınız. Bu oldukça değerli IMHO, aksi takdirde bir işlevdeki bir değişkene ne olduğunu izlemeye çalışırken, değiştirilip değiştirilmediğini belirlemek için geçirilen tüm işlevlerin başlık dosyalarını incelemeniz gerekir. Bu şekilde, sadece işaretçi ile geçirilmiş olanlara bakmanız gerekir.
smehmood

107

C ++ ve Java'da çağrı kurallarında bazı farklılıklar vardır. C ++ 'da teknik olarak sadece iki kural vardır: değere göre by-pass ve referans-by-pass, üçüncü bir by-pointer konvansiyonu (aslında bir işaretçi tipinin by-pass değeri) olan bazı literatürler. Bunun da ötesinde, semantiği artırarak argümanın türüne sabitlik ekleyebilirsiniz.

Referans ile geç

Başvuru yoluyla iletmek, işlevin kavramsal olarak nesnenin bir kopyasını değil, nesne örneğinizi alacağı anlamına gelir. Başvuru, kavramsal olarak çağıran bağlamda kullanılan nesnenin diğer adıdır ve boş olamaz. İşlev içinde gerçekleştirilen tüm işlemler, işlev dışındaki nesneye uygulanır. Bu kural Java veya C'de mevcut değildir.

Değere göre geç (ve işaretçi geçerek)

Derleyici, çağıran bağlamda nesnenin bir kopyasını oluşturur ve bu kopyayı işlevin içinde kullanır. İşlev içinde gerçekleştirilen tüm işlemler harici öğeye değil kopyaya yapılır. Bu, Java'daki ilkel türler için bir kuraldır.

Özel bir sürümü bir işaretçiye (nesnenin adresi) bir işleve geçiriyor. İşlev imleci alır ve imlecin kendisine uygulanan tüm işlemler kopyaya (imleç) uygulanır, öte yandan, kayıttan çıkarılmış imlece uygulanan işlemler o hafıza konumundaki nesne örneğine uygulanır, böylece işlev yan etkileri olabilir. Bir işaretçinin nesneye göre by-pass değerinin kullanılmasının etkisi, dahili işlevin, referans by pass ile olduğu gibi harici değerleri değiştirmesine izin verir ve isteğe bağlı değerlere de izin verir (bir boş gösterici geçirir).

Bu, bir fonksiyonun harici bir değişkeni değiştirmesi gerektiğinde C'de kullanılan kuraldır ve Java'da başvuru türleriyle kullanılır: başvuru kopyalanır, ancak başvurulan nesne aynıdır: başvuru / işaretçideki değişiklikler dışarıda görünmez işlev, ancak sivri bellekte değişiklikler vardır.

Denkleme const ekleme

C ++ 'da, farklı düzeylerde değişkenler, işaretçiler ve referanslar tanımlarken nesnelere sabitlik atayabilirsiniz. Bir değişkeni sabit olarak bildirebilir, sabit bir örneğe başvuru bildirebilir ve sabit nesnelere tüm işaretçileri, değişken nesnelere sabit işaretçileri ve sabit öğelere sabit işaretçileri tanımlayabilirsiniz. Tersine Java'da yalnızca bir sabitlik düzeyi (son anahtar kelime) tanımlayabilirsiniz: değişkenin düzeyi (ilkel türler için örnek, referans türleri için referans), ancak değişmez bir öğeye başvuru tanımlayamazsınız (sınıf kendisi değilse iletmenin).

Bu, C ++ çağrı kurallarında yaygın olarak kullanılmaktadır. Nesneler küçük olduğunda, nesneyi değere göre geçirebilirsiniz. Derleyici bir kopya oluşturur, ancak bu kopya pahalı bir işlem değildir. Diğer türler için, işlev nesneyi değiştirmezse, türün sabit bir örneğine (genellikle sabit başvuru adı verilir) bir başvuru iletebilirsiniz. Bu, nesneyi kopyalamaz, ancak işleve iletir. Ancak aynı zamanda derleyici, nesnenin işlevin içinde değiştirilmediğini garanti edecektir.

Temel kurallar

İzlenmesi gereken bazı temel kurallar şunlardır:

  • İlkel türler için by-pass değerini tercih edin
  • Diğer türler için sabite referanslarla referans by pass'ı tercih et
  • İşlevin bağımsız değişkeni değiştirmesi gerekiyorsa, doğrudan referans kullanın
  • Argüman isteğe bağlıysa, pass-by-pointer kullanın (isteğe bağlı değerin değiştirilmemesi gerekiyorsa sabitlemek için)

Bu kurallardan ilki bir nesnenin sahipliğini ele alan başka küçük sapmalar da vardır. Bir nesne dinamik olarak yeni ile ayrıldığında, delete (veya [] sürümleri) ile ayrılmalıdır. Nesnenin yok edilmesinden sorumlu olan nesne veya işlev kaynağın sahibi olarak kabul edilir. Bir kod parçasında dinamik olarak ayrılmış bir nesne oluşturulduğunda, ancak sahiplik farklı bir öğeye aktarıldığında, genellikle işaretçi anlambilimiyle veya mümkünse akıllı işaretçilerle yapılır.

Kenar notu

C ++ ve Java referansları arasındaki farkın önemi konusunda ısrar etmek önemlidir. C ++ 'da başvurular kavramsal olarak nesnenin örneğidir, ona erişimci değildir. Bunun en basit örneği bir takas işlevi uygulamaktır:

// C++
class Type; // defined somewhere before, with the appropriate operations
void swap( Type & a, Type & b ) {
   Type tmp = a;
   a = b;
   b = tmp;
}
int main() {
   Type a, b;
   Type old_a = a, old_b = b;
   swap( a, b );
   assert( a == old_b );
   assert( b == old_a ); 
}

Yukarıda takas fonksiyonu değişiklikleri referans yoluyla her iki bağımsız değişkenleri. Java'daki en yakın kod:

public class C {
   // ...
   public static void swap( C a, C b ) {
      C tmp = a;
      a = b;
      b = tmp;
   }
   public static void main( String args[] ) {
      C a = new C();
      C b = new C();
      C old_a = a;
      C old_b = b;
      swap( a, b ); 
      // a and b remain unchanged a==old_a, and b==old_b
   }
}

Kodun Java sürümü, başvuruların kopyalarını dahili olarak değiştirir, ancak gerçek nesneleri harici olarak değiştirmez. Java başvuruları, değere göre işlevlere geçirilen işaretçi aritmetiği olmayan C işaretçileridir.


4
@ david-rodriguez-dribeas Başparmak bölümünün kurallarını seviyorum, özellikle "İlkel türler için by-pass değerini tercih et"
yadab

Bana göre, bu soruya daha iyi bir cevap.
unrealsoul007

22

Dikkate alınması gereken birkaç durum vardır.

Parametre değiştirildi ("çıkış" ve "giriş / çıkış" parametreleri)

void modifies(T &param);
// vs
void modifies(T *param);

Bu durum çoğunlukla stil ile ilgilidir: kodun call (obj) veya call (& obj) gibi görünmesini ister misiniz? Ancak, farkın önemli olduğu iki nokta vardır: isteğe bağlı durum, aşağıda ve operatörleri aşırı yüklerken bir referans kullanmak istiyorsunuz.

... ve isteğe bağlı

void modifies(T *param=0);  // default value optional, too
// vs
void modifies();
void modifies(T &param);

Parametre değiştirilmedi

void uses(T const &param);
// vs
void uses(T param);

Bu ilginç bir durum. Temel kural "kopyalanması ucuz" türler değere göre geçirilir - bunlar genellikle küçük türlerdir (ancak her zaman değil) - diğerleri const ref tarafından geçirilir. Bununla birlikte, işlevinizden bağımsız olarak bir kopya oluşturmanız gerekiyorsa, değere göre geçmelisiniz . (Evet, bu biraz uygulama ayrıntısı ortaya koyar. C'est le C ++. )

... ve isteğe bağlı

void uses(T const *param=0);  // default value optional, too
// vs
void uses();
void uses(T const &param);  // or optional(T param)

Burada tüm durumlar arasında en az fark var, bu yüzden hayatınızı en kolay yapanı seçin.

Değere göre sabitleme bir uygulama detayıdır

void f(T);
void f(T const);

Bu bildirimler aslında aynı işlevdir! Değere göre geçerken, const sadece bir uygulama detayıdır. Denemek:

void f(int);
void f(int const) { /* implements above function, not an overload */ }

typedef void NC(int);       // typedefing function types
typedef void C(int const);

NC *nc = &f;  // nc is a function pointer
C *c = nc;    // C and NC are identical types

3
+1 Değerden constgeçerken uygulama olmayı bilmiyordum .
balki

20

Değere göre geç:

void func (vector v)

İşlev, ortamdan tam bir yalıtıma ihtiyaç duyduğunda, örneğin işlevin orijinal değişkeni değiştirmesini önlemek ve işlev yürütülürken diğer iş parçacıklarının değerini değiştirmesini önlemek için değişkenleri değere göre iletin.

Dezavantajı, CPU döngüleri ve nesneyi kopyalamak için harcanan ekstra bellektir.

Const referansı ile geçin:

void func (const vector& v);

Bu form, kopyalama yükünü kaldırırken her bir değere göre davranışı öykünür. İşlev, orijinal nesneye okuma erişimi alır, ancak değerini değiştiremez.

Dezavantajı iplik güvenliğidir: orijinal nesnede başka bir iş parçacığı tarafından yapılan herhangi bir değişiklik, işlev yürütülürken işlevin içinde görünecektir.

Sabit olmayan referansla geçiş:

void func (vector& v)

İşlev değişkene bir değer geri yazmak zorunda olduğunda bunu kullanın, sonuçta arayan tarafından kullanılır.

Const referans durumunda olduğu gibi, bu iş parçacığı için güvenli değildir.

Sabit işaretçiyle geçin:

void func (const vector* vp);

İşlevsel olarak, farklı sözdizimi dışında const-başvurusuyla geçişle aynıdır, ayrıca çağıran işlevin, iletilecek geçerli veri olmadığını belirtmek için NULL işaretçisini geçebileceği gerçeği.

İplik güvenli değildir.

Sabit olmayan işaretçi ile geçiş:

void func (vector* vp);

Sabit olmayan referansa benzer. İşlev bir değer geri yazması gerekmediğinde, arayan genellikle değişkeni NULL olarak ayarlar. Bu kural birçok glibc API'sinde görülür. Misal:

void func (string* str, /* ... */) {
    if (str != NULL) {
        *str = some_value; // assign to *str only if it's non-null
    }
}

Tüm referans / işaretçi ile olduğu gibi, iş parçacığı için güvenli değil.


0

Kimsenin üzerine eklemediğimden bahsetmediği için, c ++ 'da bir işleve bir nesne ilettiğinizde, nesnenin bir klonunu oluşturan ve sonra yönteme geçiren bir nesneniz yoksa, nesnenin varsayılan kopya yapıcısı çağrılır. özgün nesne yerine nesnenin kopyasını yansıtacak nesne değerlerini değiştirdiğinizde, bu c ++ 'da sorun, yani tüm sınıf özniteliklerini işaretçi yaparsanız, kopya yapıcılar işaretçi öznitelikleri, bu nedenle yöntem işaretçi öznitelik adreslerinde depolanan değerleri işleyen nesne üzerinde çağrıldığında, değişiklikler parametre olarak iletilen orijinal nesneye de yansır, böylece bu bir Java ile aynı şekilde davranabilir, ancak tüm sınıfınızın öznitelikler işaretçiler olmalı, ayrıca işaretçilerin değerlerini değiştirmelisiniz,kod açıklaması ile çok açık olacak.

Class CPlusPlusJavaFunctionality {
    public:
       CPlusPlusJavaFunctionality(){
         attribute = new int;
         *attribute = value;
       }

       void setValue(int value){
           *attribute = value;
       }

       void getValue(){
          return *attribute;
       }

       ~ CPlusPlusJavaFuncitonality(){
          delete(attribute);
       }

    private:
       int *attribute;
}

void changeObjectAttribute(CPlusPlusJavaFunctionality obj, int value){
   int* prt = obj.attribute;
   *ptr = value;
}

int main(){

   CPlusPlusJavaFunctionality obj;

   obj.setValue(10);

   cout<< obj.getValue();  //output: 10

   changeObjectAttribute(obj, 15);

   cout<< obj.getValue();  //output: 15
}

Ancak bu, bellek sızıntılarına eğilimli olan ve yıkıcıları çağırmayı unutmayan işaretçilerle çok fazla kod yazacağınız için iyi bir fikir değildir. Ve bu c ++ önlemek için işaretçiler içeren nesneler diğer nesne verilerini işlemeyi durduracak işlev argümanlarına geçirildiğinde yeni bellek oluşturacak kopya yapıcılar var, Java değer ve değer tarafından geçiyor referans, bu nedenle kopya yapıcılar gerektirmez.


-1

Bir nesneyi parametre olarak bir işleve geçirmenin üç yöntemi vardır:

  1. Referans ile geç
  2. değere göre geçmek
  3. parametreye sabit ekleme

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

class Sample
{
public:
    int *ptr;
    int mVar;

    Sample(int i)
    {
        mVar = 4;
        ptr = new int(i);
    }

    ~Sample()
    {
        delete ptr;
    }

    void PrintVal()
    {
        cout << "The value of the pointer is " << *ptr << endl
             << "The value of the variable is " << mVar;
   }
};

void SomeFunc(Sample x)
{
cout << "Say i am in someFunc " << endl;
}


int main()
{

  Sample s1= 10;
  SomeFunc(s1);
  s1.PrintVal();
  char ch;
  cin >> ch;
}

Çıktı:

Am i
someFunc deyin İşaretçinin değeri -17891602'dir
Değişkenin değeri 4'tür


Sadece 2 yöntem var (bahsettiğiniz ilk 2 yöntem). "Parametrede sabit geçme" ile ne demek istediğinizi bilmiyorum
MM

-1

Aşağıdakiler, C ++ ile çalışmak üzere bir argüman / parametre iletmenin yollarıdır.

1. değere göre.

// passing parameters by value . . .

void foo(int x) 
{
    x = 6;  
}

2. referans ile.

// passing parameters by reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  
}

// passing parameters by const reference . . .

void foo(const int &x) // x is a const reference
{
    x = 6;  // compile error: a const reference cannot have its value changed!
}

3. nesneye göre.

class abc
{
    display()
    {
        cout<<"Class abc";
    }
}


// pass object by value
void show(abc S)
{
    cout<<S.display();
}

// pass object by reference
void show(abc& S)
{
    cout<<S.display();
}

1
"nesneden geçmek" bir şey değildir. Sadece değere göre geçiş ve referans ile geçiş vardır. "Durum 3", aslında değere göre bir geçiş durumunu ve referans olarak bir geçiş durumunu gösterir.
MM
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.