önsöz
Java, hype'ın aksine, C ++ gibi bir şey değildir. Java hype makinesi, Java'nın C ++ benzeri sözdizimine sahip olduğundan, dillerin benzer olduğuna inanmanızı ister. Hiçbir şey gerçeğin ötesinde olamaz. Bu yanlış bilgi, Java programcılarının kodlarının sonuçlarını anlamadan C ++ 'a gitme ve Java benzeri sözdizimi kullanma nedeninin bir parçasıdır.
İleri gidiyoruz
Ama bunu neden bu şekilde yapmalıyız. Bellek adresine doğrudan erişebildiğimiz için bunun verimlilik ve hız ile ilgili olduğunu varsayabilirim. Haklı mıyım?
Aksine, aslında. Yığın yığına göre çok daha yavaştır , çünkü yığın yığına göre çok basittir. Otomatik depolama değişkenleri (yığın değişkenleri olarak da bilinir) kapsam dışı olduktan sonra yıkıcılarını çağırır. Örneğin:
{
std::string s;
}
// s is destroyed here
Öte yandan, dinamik olarak ayrılmış bir işaretçi kullanırsanız, yıkıcısı manuel olarak çağrılmalıdır. delete
bu yıkıcıyı sizin için çağırıyor.
{
std::string* s = new std::string;
}
delete s; // destructor called
Bunun new
C # ve Java'daki sözdizimi ile ilgisi yoktur . Tamamen farklı amaçlar için kullanılırlar.
Dinamik ayırmanın faydaları
1. Dizinin boyutunu önceden bilmek zorunda değilsiniz
Birçok C ++ programcısının karşılaştığı ilk sorunlardan biri, kullanıcılardan rasgele girdi kabul ettiklerinde, yalnızca bir yığın değişkeni için sabit bir boyut ayırabilmenizdir. Dizilerin boyutunu da değiştiremezsiniz. Örneğin:
char buffer[100];
std::cin >> buffer;
// bad input = buffer overflow
Tabii ki, eğer bir std::string
yerine kullandıysanız , std::string
dahili olarak kendisini yeniden boyutlandırır , böylece bir sorun olmamalıdır. Ama esasen bu sorunun çözümü dinamik tahsis. Kullanıcının girişine göre dinamik bellek ayırabilirsiniz, örneğin:
int * pointer;
std::cout << "How many items do you need?";
std::cin >> n;
pointer = new int[n];
Yan not : Yeni başlayanların yaptığı bir hata, değişken uzunluklu dizilerin kullanılmasıdır. Bu bir GNU uzantısı ve aynı zamanda Clang'da bir tane çünkü GCC'nin uzantılarının çoğunu yansıtıyorlar. Bu nedenle aşağıdakilere
int arr[n]
güvenilmemelidir.
Yığın yığından çok daha büyük olduğu için, kişinin istediği kadar hafızayı keyfi olarak tahsis edebilir / yeniden tahsis edebilirken, yığının bir sınırlaması vardır.
2. Diziler işaretçi değildir
Bu sorduğunuz bir fayda nedir? Dizilerin ve işaretçilerin arkasındaki karışıklığı / efsaneyi anladığınızda cevap netleşecektir. Çoğunlukla aynı oldukları varsayılır, ancak değildir. Bu efsane, işaretçilerin tıpkı diziler gibi abone olabileceğinden ve dizilerin işlev bildiriminde en üst düzeydeki işaretçilere bozunmasından kaynaklanmaktadır. Ancak, bir dizi bir işaretçiye bozulduğunda, işaretçi sizeof
bilgilerini kaybeder . Böylece sizeof(pointer)
işaretçinin boyutunu bayt cinsinden verir, bu da 64 bit sistemde genellikle 8 bayttır.
Dizilere atayamazsınız, yalnızca başlatabilirsiniz. Örneğin:
int arr[5] = {1, 2, 3, 4, 5}; // initialization
int arr[] = {1, 2, 3, 4, 5}; // The standard dictates that the size of the array
// be given by the amount of members in the initializer
arr = { 1, 2, 3, 4, 5 }; // ERROR
Öte yandan, işaretçilerle istediğinizi yapabilirsiniz. Ne yazık ki, işaretçiler ve diziler arasındaki ayrım Java ve C # 'da el sallandığından, yeni başlayanlar farkı anlamıyor.
3. Çok Biçimlilik
Java ve C #, örneğin as
anahtar kelimeyi kullanarak nesnelere başka bir şey gibi davranmanıza izin veren olanaklara sahiptir . Birisi bir Entity
nesneyi bir nesne olarak ele almak isterse Player
, bunu yapabilirdi Player player = Entity as Player;
Bu, yalnızca belirli bir tür için geçerli olması gereken homojen bir kapta işlevleri çağırmak istiyorsanız çok kullanışlıdır. İşlevsellik aşağıda benzer bir şekilde elde edilebilir:
std::vector<Base*> vector;
vector.push_back(&square);
vector.push_back(&triangle);
for (auto& e : vector)
{
auto test = dynamic_cast<Triangle*>(e); // I only care about triangles
if (!test) // not a triangle
e.GenericFunction();
else
e.TriangleOnlyMagic();
}
Diyelim ki sadece Üçgenler bir Döndürme işlevine sahip olsaydı, sınıfın tüm nesnelerinde çağırmayı denediyseniz derleyici hatası olurdu. Düğmesini kullanarak anahtar kelimeyi dynamic_cast
simüle edebilirsiniz as
. Açıkça belirtmek gerekirse, bir yayın başarısız olursa geçersiz bir işaretçi döndürür. Yani !test
aslında test
NULL olup olmadığını kontrol etmek için bir kısayol veya geçersiz bir işaretçi, yani döküm başarısız oldu.
Otomatik değişkenlerin faydaları
Dinamik ayırmanın yapabileceği tüm harika şeyleri gördükten sonra, muhtemelen neden hiç kimse dinamik ayırmayı sürekli KULLANMAYACAKSINIZ merak ediyorsunuz? Sana zaten bir nedenden bahsetmiştim, yığın yavaş. Ve tüm bu belleğe ihtiyacınız yoksa, onu kötüye kullanmamalısınız. İşte belirli bir sırayla bazı dezavantajlar:
Hata eğilimli. Manuel bellek ayırma tehlikelidir ve sızıntı yapmaya eğilimlisiniz. Hata ayıklayıcıyı veya valgrind
(bir bellek sızıntısı aracını) kullanma konusunda yetkin değilseniz, saçınızı başınızdan çekebilirsiniz. Neyse ki RAII deyimleri ve akıllı işaretçiler bunu biraz hafifletir, ancak Üçün Kuralı ve Beşin Kuralı gibi uygulamalara aşina olmalısınız. Almak için çok fazla bilgi var ve bilmeyen ya da umursamayan yeni başlayanlar bu tuzağa düşecek.
Bu gerekli değil. new
Anahtar kelimeyi her yerde kullanmanın deyimsel olduğu Java ve C # 'dan farklı olarak , C ++' da bunu sadece ihtiyacınız olduğunda kullanmalısınız. Ortak ifade gider, bir çekiç varsa her şey çivi gibi görünür. C ++ ile başlayan yeni başlayanlar işaretçilerden korkar ve yığın değişkenlerini alışkanlıkla kullanmayı öğrenirken, Java ve C # programcıları işaretçileri anlamadan kullanmaya başlarlar ! Bu tam anlamıyla yanlış ayağa çıkıyor. Bildiğiniz her şeyi terk etmelisiniz çünkü sözdizimi bir şeydir, dili öğrenmek başka bir şeydir.
1. (N) RVO - Aka, (Adlandırılmış) Dönüş Değeri Optimizasyonu
Birçok derleyicinin yaptığı bir optimizasyon, elision ve dönüş değeri optimizasyonu olarak adlandırılan şeylerdir . Bunlar, çok sayıda öğe içeren bir vektör gibi çok büyük nesneler için yararlı olan gereksiz kopyaları ortadan kaldırabilir. Normalde yaygın bir uygulama için işaretçileri kullanmaktır transfer sahipliği ziyade büyük nesneleri kopyalama daha hareket onları etrafında. Bu, hareket semantiği ve akıllı işaretçilerin başlamasına neden oldu .
Eğer işaretçileri kullanıyorsanız, (K) RVO gelmez DEĞİL meydana gelir. Optimizasyon konusunda endişeleniyorsanız, işaretçiler döndürmek veya geçmek yerine (N) RVO'dan faydalanmak daha yararlı ve daha az hataya açıktır. Bir fonksiyonun arayanı delete
dinamik olarak tahsis edilmiş bir nesne ve benzeri şeylerden sorumluysa hata sızıntıları meydana gelebilir . İşaretçiler bir sıcak patates gibi geçiriliyorsa, bir nesnenin sahipliğini izlemek zor olabilir. Daha basit ve daha iyi olduğu için yığın değişkenlerini kullanın.