Açıkça yapmazsam C ++ sınıf üyeleri nasıl başlatılır?


158

Ben özel Eklenmiþ sahip bir sınıf olduğunu varsayalım ptr, name, pname, rname, crnameve age. Onları kendim başlatmazsam ne olur? İşte bir örnek:

class Example {
    private:
        int *ptr;
        string name;
        string *pname;
        string &rname;
        const string &crname;
        int age;

    public:
        Example() {}
};

Ve sonra yaparım:

int main() {
    Example ex;
}

Üyeler ex'de nasıl başlatılır? İşaretçilerle ne olur? Do stringve intalmak varsayılan kurucular ile 0-intialized string()ve int()? Referans üye ne olacak? Peki ya const referansları?

Başka ne bilmeliyim?

Herkes bu vakaları kapsayan bir öğretici biliyor mu? Belki bazı kitaplarda? Üniversite kütüphanesinde birçok C ++ kitabına erişimim var.

Daha iyi (hatasız) programlar yazabilmek için öğrenmek istiyorum. Herhangi bir geri bildirim yardımcı olacaktır!


3
Kitap önerileri için bkz. Stackoverflow.com/questions/388242/…
Mike Seymour

Mike, yani, bunu anlatan bir kitaptan bölüm. Bütün kitap değil! :)
bodacydo

Yine de, programlamak istediğiniz dilde bir kitabın tamamını okumak iyi bir fikir olacaktır. Zaten birini okuduysanız ve bunu açıklamıyorsa, o zaman çok iyi bir kitap değildi.
Tyler McHenry

2
Scott Meyers (popüler bir ex-pro C ++ tavsiye gurusu), Effective C ++ 'da "kurallar karmaşıktır - ezberlemeye değmeyecek kadar karmaşık, bence .... tüm kurucuların nesnedeki her şeyi başlattığından emin olun." Yani onun görüşüne göre, (girişimi) yazma "böcek özgür" kod en kolay yolu (ve aslında o gelmez kuralları ezberlemek denemek için değil değil kitapta onları yatıyordu), ancak açıkça initialize her şeyi. Bununla birlikte, bu yaklaşımı kendi kodunuzda alsanız bile, bilmeyen kişiler tarafından yazılmış projeler üzerinde çalışabileceğinizi unutmayın, bu nedenle kurallar hala değerli olabilir.
Kyle Strand

2
@TylerMcHenry C ++ 'da hangi kitapları "iyi" olarak değerlendiriyorsunuz? C ++ ile ilgili birkaç kitap okudum, ama hiçbiri bunu tam olarak açıklamadı. Önceki yorumumda belirtildiği gibi, Scott Meyers , Effective C ++ ' da tüm kuralları sağlamayı açıkça reddetmektedir . Meyers'ın Etkili Modern C ++ , Dewhurst'un C ++ Ortak Bilgisi ve Stroustrup'un C ++ Turu'nu da okudum . Hafızama, hiçbiri kuralları açıklamamıştı. Açıkçası ben standart okumak olabilir, ama neredeyse "iyi bir kitap" düşünün olmaz! : D Ve Stroustrup muhtemelen C ++ Programlama Dili açıklar bekliyoruz .
Kyle Strand

Yanıtlar:


207

Açık başlatma yerine, sınıflardaki üyelerin başlatılması, işlevlerdeki yerel değişkenlerin başlatılmasıyla aynı şekilde çalışır.

İçin nesneler , varsayılan kurucu denir. Örneğin std::string, varsayılan kurucu onu boş bir dizeye ayarlar. Nesnenin sınıfının varsayılan bir yapıcısı yoksa, açıkça başlatmazsanız derleme hatası olur.

İçin basit türler (işaretçiler, ints, vb), onlar vardır değil başlatıldı - onlar keyfi önemsiz önceden o hafıza yerde olmak ne oldu içerirler.

İçin referanslar (örn std::string&), öyle yasadışı bunları başlatmak için değil, ve derleyici şikayet ve böyle bir kod derlemeye reddeder. Referanslar her zaman başlatılmalıdır.

Dolayısıyla, özel durumunuzda, açıkça başlatılmamışlarsa:

    int *ptr;  // Contains junk
    string name;  // Empty string
    string *pname;  // Contains junk
    string &rname;  // Compile error
    const string &crname;  // Compile error
    int age;  // Contains junk

4
+1. Sıkı standart tanım ile, ilkel tiplerin örneklerinin yanı sıra çeşitli diğer şeylerin (herhangi bir depolama bölgesi) hepsinin nesne olarak kabul edildiğini belirtmek gerekir .
27:10

7
"Nesnenin sınıfının varsayılan bir yapıcısı yoksa, açıkça başlatmazsanız bu derleme hatası olacaktır" yanlış ! Bir sınıfın varsayılan yapıcısı yoksa , boş olan varsayılan bir varsayılan yapıcı verilir .
Sihirbaz

19
@wiz Bence kelimenin tam anlamıyla 'nesnenin varsayılan bir yapıcı yoksa' ya da üretilen herhangi biri gibi anlamına geliyordu, sınıf açıkça varsayılan dışında herhangi bir kurucu tanımlar (varsayılan ctor oluşturulamaz) durum böyle olacağını düşünüyorum. Çok bilgiçlik kazanırsak, muhtemelen yardım etmekten daha fazlasını karıştıracağız ve Tyler daha önce bana verdiği yanıtta bu konuda iyi bir noktaya değindi.
27:10

8
@ wiz-loz Yapıcısı olduğunu söyleyebilirim, foosadece örtük. Ama bu gerçekten bir anlambilim argümanı.
Tyler McHenry

4
"Default constructor" ı argüman olmadan çağrılabilen bir kurucu olarak yorumluyorum. Bu, kendinizi tanımladığınız veya derleyici tarafından örtük olarak oluşturulmuş olacaktır. Yani eksikliği, kendiniz tanımladığınız veya üretmediğiniz anlamına gelir. Ya da ben böyle görüyorum.
5

28

Önce mem-başlatıcı listesinin ne olduğunu açıklayayım . Bir mem-başlatıcı-listesi , her mem-başlatıcısının bir üye adı ve ardından bir ifade listesi ve ardından a, virgülle ayrılmış bir mem-başlatıcıları listesidir . İfadesi listesi üyesi oluşturulur nasıl. Örneğin,()

static const char s_str[] = "bodacydo";
class Example
{
private:
    int *ptr;
    string name;
    string *pname;
    string &rname;
    const string &crname;
    int age;

public:
    Example()
        : name(s_str, s_str + 8), rname(name), crname(name), age(-4)
    {
    }
};

Mem-başlatıcı-liste kullanıcı tarafından sağlanan, hiç argümanlar yapıcısı taşımaktadır name(s_str, s_str + 8), rname(name), crname(name), age(-4). Bu mem-başlatıcı listesi , nameüyenin iki giriş yineleyicisi alan std::stringyapıcı tarafından başlatıldığı , rnameüye bir referansla başlatılır name, crnameüye bir const referansı ile başlatılır nameve ageüye değerle başlatılır -4.

Her kurucunun kendi mem-başlatıcı listesi vardır ve üyeler yalnızca önceden belirlenmiş bir sırayla (temel olarak üyelerin sınıfta beyan edildikleri sıraya göre) başlatılabilir. Bu nedenle, üyeleri Exampletek amacıyla başlatıldı: ptr, name, pname, rname, crname, ve age.

Bir üyenin mem başlatıcısı belirtmediğinizde , C ++ standardı şöyle der:

Varlık, sınıf ... türünde statik olmayan bir veri üyesiyse, varlık varsayılan olarak başlatılır (8.5). ... Aksi takdirde varlık başlatılmaz.

Burada, namesınıf türünde statik olmayan bir veri üyesi olduğu için name, mem başlatıcı listesinde herhangi bir başlatıcı belirtilmemişse varsayılan olarak başlatılır . Diğer tüm üyelerin Examplesınıf türü yoktur, bu nedenle başlatılmazlar.

Standart başlatılmadıklarını söylediğinde, bu onların herhangi bir değere sahip olabileceği anlamına gelir . Dolayısıyla, yukarıdaki kod başlatılmadığından pname, herhangi bir şey olabilir.

Referansların her zaman başlatılması gereken kural gibi başka kurallara uymanız gerektiğini unutmayın. Referansları başlatmamak bir derleyici hatasıdır.


Çok fazla dahili bilgi göstermeden bildirimi (in .h) ve tanımı (in .cpp) kesinlikle ayırmak istediğinizde üyeleri başlatmanın en iyi yolu budur .
Matthieu

12

Veri üyelerini, bildirdiğiniz noktada da başlatabilirsiniz:

class another_example{
public:
    another_example();
    ~another_example();
private:
    int m_iInteger=10;
    double m_dDouble=10.765;
};

Bu formu hemen hemen tamamen kullanıyorum, ancak bazı insanların onu 'kötü form' olarak gördüğünü okudum, ancak belki de son zamanlarda tanıtıldığı için - C ++ 11'de düşünüyorum. Bana göre daha mantıklı.

Yeni kuralların bir diğer yararlı yönü, kendileri sınıf olan veri üyelerinin nasıl başlatılacağıdır. Diyelim ki CDynamicStringdize işlemeyi içine alan bir sınıf. Başlangıç ​​değerini belirlemenizi sağlayan bir yapıcıya sahiptir CDynamicString(wchat_t* pstrInitialString). Bu sınıfı başka bir sınıfın içindeki bir veri üyesi olarak kullanabilirsiniz - bu durumda bir posta adresini depolayan bir Windows kayıt defteri değerini kapsayan bir sınıf. Parantez kullandığınız kayıt defteri anahtarının adını 'sabit kodlamak' için:

class Registry_Entry{
public:
    Registry_Entry();
    ~Registry_Entry();
    Commit();//Writes data to registry.
    Retrieve();//Reads data from registry;
private:
    CDynamicString m_cKeyName{L"Postal Address"};
    CDynamicString m_cAddress;
};

Gerçek posta adresini tutan ikinci dize sınıfının bir başlatıcısı olmadığına dikkat edin, bu nedenle varsayılan kurucusu oluşturma sırasında çağrılır - belki de otomatik olarak boş bir dizeye ayarlanır.


9

Örnek sınıf yığın üzerinde başlatılırsa, başlatılmamış skaler üyelerin içeriği rasgele ve tanımsızdır.

Genel bir örnek için, başlatılmamış skaler üyeler sıfırlanır.

Kendileri sınıf örneği olan üyeler için varsayılan kurucuları çağrılır, böylece dize nesneniz başlatılır.

  • int *ptr; // başlatılmamış işaretçi (veya global ise sıfırlanmış)
  • string name; // kurucu çağrıldı, boş dize ile başlatıldı
  • string *pname; // başlatılmamış işaretçi (veya global ise sıfırlanmış)
  • string &rname; // bunu başlatamazsanız derleme hatası
  • const string &crname; // bunu başlatamazsanız derleme hatası
  • int age; // skaler değer, başlatılmamış ve rastgele (veya global ise sıfırlanmış)

Denedim ve string nameyığın üzerinde sınıfı başlattıktan sonra boş görünüyor . Cevabınızdan kesinlikle emin misin?
Haziran'da bodacydo

1
string, varsayılan olarak boş bir string sağlayan bir kurucuya sahip olacak - Cevabımı açıklığa kavuşturacağım
Paul Dixon

@bodacydo: Paul doğrudur, ancak bu davranışı önemsiyorsanız, asla açık olmaktan acıtmaz. Başlatıcı listesine atın.
Stephen

Açıkladığınız ve açıkladığınız için teşekkürler!
Haziran'da bodacydo

2
Rastgele değil! Rastgele bunun için çok büyük bir kelime! Skaler üyeler rastgele olsaydı, başka rastgele sayı üreteçlerine ihtiyacımız olmazdı. Verileri "kalanları" analiz eden bir program düşünün - bellekteki silinmemiş dosyalar gibi - veriler rastgele olmaktan uzaktır. Tanımsız bile değil! Tanımlamak genellikle zordur, çünkü genellikle makinemizin ne yaptığını bilmiyoruz. Eğer sadece geri silme "rastgele veri" babanızın tek görüntü ise, rastgele olduğunu söylerseniz anneniz bile saldırgan bulabilirsiniz ...
slyy2048

5

Başlatılmamış statik olmayan üyeler rastgele veriler içerir. Aslında, sadece atandıkları bellek konumu değerine sahip olacaklar.

Tabii ki nesne parametreleri için (gibi string) nesnenin yapıcısı varsayılan bir başlatma yapabilir.

Örneğinizde:

int *ptr; // will point to a random memory location
string name; // empty string (due to string's default costructor)
string *pname; // will point to a random memory location
string &rname; // it would't compile
const string &crname; // it would't compile
int age; // random value

2

Bir kurucuya sahip üyelerin varsayılan kurucuları başlatma için çağrılacaktır.

Diğer türlerin içeriğine güvenemezsiniz.


0

Yığın üzerindeyse, kendi kurucuları olmayan başlatılmamış üyelerin içeriği rastgele ve tanımsız olacaktır. Küresel olsa bile, bunların sıfırlanmasına güvenmek kötü bir fikir olacaktır. Yığında olsun ya da olmasın, bir üyenin kendi yapıcısı varsa, onu başlatmak için çağrılacaktır.

Yani, * pname dizeniz varsa, işaretçi rastgele önemsiz içerir. ancak dize adı için, dize için varsayılan yapıcı çağrılır ve size boş bir dize verilir. Referans türü değişkenleriniz için emin değilim, ancak muhtemelen bazı rastgele bellek yığınlarına bir referans olacaktır.


0

Sınıfın nasıl inşa edildiğine bağlıdır

Bu soruyu cevaplamak, C ++ dil standardında büyük bir anahtar durum ifadesini ve sadece ölümlülerin sezgi alması zor olanı anlamaya geliyor.

İşin ne kadar zor olduğuna dair basit bir örnek olarak:

main.cpp

#include <cassert>

int main() {
    struct C { int i; };

    // This syntax is called "default initialization"
    C a;
    // i undefined

    // This syntax is called "value initialization"
    C b{};
    assert(b.i == 0);
}

Varsayılan başlatma işleminde şu konumdan başlıyorsunuz: https://en.cppreference.com/w/cpp/language/default_initialization "Varsayılan başlatmanın etkileri" bölümüne gideriz ve vaka bildirimini başlatırız:

  • "Eğer T POD değilse ": hayır (POD'un tanımı kendi başına büyük bir anahtar deyimidir)
  • "T bir dizi türüyse": hayır
  • "aksi halde hiçbir şey yapılmaz": bu nedenle tanımlanmamış bir değerle bırakılır

Daha sonra, birisi değer atamaya karar verirse, https://en.cppreference.com/w/cpp/language/value_initialization "Değer başlatmanın etkileri" olur ve vaka bildirimini başlatırız:

  • "T varsayılan kurucu olmayan veya kullanıcı tarafından sağlanan veya silinmiş varsayılan kurucu bulunan bir sınıf tipiyse": durum böyle değil. Şimdi 20 dakika harcayacaksınız.
    • örtük olarak tanımlanmış bir varsayılan kurucumuz var (özellikle başka kurucu tanımlanmadığı için)
    • kullanıcı tarafından sağlanmaz (örtülü olarak tanımlanır)
    • silinmez ( = delete)
  • "T, kullanıcı tarafından sağlanan veya silinmeyen varsayılan oluşturucuya sahip bir sınıf türüyse": Evet
    • "nesne sıfırdan başlatılır ve sonra önemsiz olmayan bir varsayılan kurucuya sahipse varsayılan olarak başlatılır": önemsiz olmayan bir kurucu yok, sadece sıfırla başlatır. "Sıfır başlangıç" tanımı en azından basittir ve beklediğinizi yapar: https://en.cppreference.com/w/cpp/language/zero_initialization

Bu yüzden asla "örtük" sıfır başlatmaya güvenmemenizi şiddetle tavsiye ederim. Güçlü performans nedenleri olmadıkça, ya tanımladıysanız kurucuda ya da toplam başlatma kullanarak her şeyi açıkça başlatın. Aksi takdirde gelecekteki geliştiriciler için işleri çok riskli hale getirirsiniz.

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.