Yeni ve silme operatörleri hakkında her şeyi biliyor muyuz? Dizileri uygulamak için yeni silme özelliğini kullanma

  • 20.04.2019

C ++ üç ana türü destekler dışkılar (dağıtım) hafıza, bunlardan ikisine zaten aşina olduğumuz:

Statik bellek ayırma ve değişkenler için çalıştırılır. Bellek, program başladığında bir kez tahsis edilir ve tüm program boyunca tutulur.

Otomatik bellek ayırma ve için yapılır. Bellek, bu değişkenleri içeren bloğa girerken tahsis edilir ve bırakıldığında serbest bırakılır.

bu makalenin konusu.

Hem statik hem de otomatik bellek ayırmanın iki ortak noktası vardır:

Değişkenin / dizinin boyutu derleme sırasında bilinmelidir.

Bellek ayırma ve serbest bırakma otomatik olarak gerçekleşir (bir değişken oluşturulduğunda veya yok edildiğinde).

Çoğu durumda, bu sorun değil. Bununla birlikte, harici girişle çalışma söz konusu olduğunda, bu kısıtlamalar sorunlara yol açabilir.

Örneğin, bir adı saklamak için kullanıldığında, kullanıcı onu girene kadar ne kadar süreceğini önceden bilemeyiz. Veya diskten bir değişkene kayıt sayısını yazmamız gerektiğinde, ancak bu kayıtların kaç tane olduğunu önceden bilmiyoruz. Veya değişken sayıda canavar içeren bir oyun oluşturabiliriz (oyun sırasında bazı canavarlar ölür, diğerleri doğar), böylece oyuncuyu öldürmeye çalışırız.

Derleme zamanında tüm değişkenlerin boyutunu bildirmemiz gerekiyorsa, yapabileceğimiz en iyi şey, bunların yeterli olacağını umarak maksimum boyutlarını tahmin etmeye çalışmaktır:

karakter ismi; // umalım kullanıcı 30 karakterden az bir isim girmiş olsun! Kayıt kaydı; // umarım 400'den fazla kayıt olmaz! Canavar canavar; // maksimum 30 canavar Çokgen oluşturma; // bu 3B oluşturma, 40.000'den az poligonla daha iyidir!

Bu, en az üç nedenden dolayı kötü bir karardır:

İlk olarak, değişkenler gerçekten kullanılmazsa veya kullanılırsa, ancak tam olarak değilse bellek kaybedilir. Örneğin, her isim için 30 karakter ayırırsak, ancak isimler ortalama 15 karakter alacaksa, o zaman hafıza tüketimi gerçekte ihtiyaç duyduğunun iki katı olacaktır. Veya oluşturma dizisini düşünün: Yalnızca 20.000 çokgen kullanıyorsa, 20.000 poligonlu bellek gerçekten boşa harcanır (yani kullanılmaz)!

İkinci olarak, çoğu sıradan değişken için bellek (sabit diziler dahil) özel bir bellek rezervuarından ayrılır - yığmak... Bir programdaki yığın bellek miktarı genellikle küçüktür - Visual Studio'da varsayılan olarak 1 MB'dir. Bu sayıyı aşarsanız, o zaman yığın taşmasıve işletim sistemi programınızı otomatik olarak sonlandıracaktır.

Visual Studio'da, aşağıdaki programı çalıştırarak bunu kontrol edebilirsiniz:

int main () (int dizi; // 1 milyon tam sayı değeri ayır)

1MB bellek sınırı, özellikle grafiklerin kullanıldığı birçok program için sorunlu olabilir.

Üçüncüsü ve en önemlisi, yapay kısıtlamalara ve / veya dizi taşmasına yol açabilir. Kullanıcı diskten 500 kayıt okumaya çalışırsa, ancak biz maksimum 400 bellek ayırmışsak ne olur? Ya kullanıcıya maksimum kayıt sayısının 400 olduğuna dair bir hata göstereceğiz ya da (en kötü durumda) dizi taşacak ve sonra çok kötü bir şey olacak.

Neyse ki, dinamik bellek ayırma bu sorunları kolayca çözebilir. Dinamik bellek ayırma Gerektiğinde programları çalıştırarak işletim sisteminden bellek talep etmenin bir yoludur. Bu bellek, program yığınının sınırlı belleğinden değil, işletim sistemi tarafından yönetilen çok daha büyük depolamadan ayrılmıştır - yığın (yığınlar) . Modern bilgisayarlarda, yığın boyutu gigabayt bellek olabilir.

Değişkenlerin dinamik tahsisi

Dinamik olarak bir değişken için bellek ayırmak için operatörü kullanın yeni:

yeni int; // dinamik olarak bir tamsayı değişkeni ayırın ve sonucu hemen atın (çünkü onu hiçbir yere kaydetmiyoruz)

Yukarıdaki örnekte, işletim sisteminden bir tamsayı değişkeni için bellek tahsisi talep ediyoruz. Yeni operatör, ayrılmış belleğin adresini içeren geri döner.

Ayrılan belleğe erişmek için bir işaretçi oluşturulur:

int * ptr \u003d yeni int; // dinamik olarak bir tamsayı değişkeni ayırın ve adresini ptr'ye atayın, böylece daha sonra ona erişebiliriz

Ardından, değeri elde etmek için işaretçiye başvurabiliriz:

* ptr \u003d 8; // 8 değerini yeni ayrılan belleğe atayın

İşte işaretçilerin yararlı olduğu zamanlardan biri. Yeni ayrılan belleğe bir adres içeren bir işaretçi olmadan, ona erişmemizin bir yolu olmazdı.

Dinamik bellek tahsisi nasıl çalışır?

Bilgisayarınızda uygulamalar tarafından kullanılabilen bellek (belki de çoğu) vardır. Bir uygulamayı başlattığınızda, işletim sisteminiz bu uygulamayı bu belleğin bir kısmına yükler. Ve uygulamanız tarafından kullanılan bu bellek, her biri belirli bir görevi yerine getiren birkaç bölüme ayrılmıştır. Bir kısım kodunuzu içerir, diğeri ortak işlemleri gerçekleştirmek için kullanılır (hangi fonksiyonların çağrıldığını takip etmek, global ve yerel değişkenleri oluşturmak ve yok etmek, vb.). Bunun hakkında daha sonra konuşacağız. Bununla birlikte, kullanılabilir belleğin çoğu orada oturur ve programlardan tahsis taleplerini bekler.

Belleği dinamik olarak ayırdığınızda, işletim sisteminden bu belleğin bir kısmını programınızın kullanması için ayırmasını istersiniz. İşletim sistemi bu isteği yerine getirebilirse, bu belleğin adresi uygulamanıza geri gönderilir. Şu andan itibaren uygulamanız bu hafızayı istediği anda kullanabilir. Bu bellekle gerekli olan her şeyi zaten tamamladığınızda, diğer istekler arasında dağıtım için işletim sistemine geri döndürülmesi gerekir.

Statik veya otomatik bellek ayırmanın aksine, programın kendisi dinamik olarak ayrılmış belleği istemekten ve geri vermekten sorumludur.

Dinamik olarak ayrılmış değişkenleri başlatma

Bir değişkeni dinamik olarak ayırdığınızda, onu aynı zamanda veya tek tip başlatma yoluyla da başlatabilirsiniz (C ++ 11'de):

int * ptr1 \u003d yeni int (7); // doğrudan başlatma kullan int * ptr2 \u003d new int (8); // tek tip başlatma kullan

Değişkenleri Silme

Dinamik olarak ayrılmış bir değişkenle ihtiyaç duyulan her şey zaten yapıldığında, C ++ 'ya bu belleği boşaltmasını açıkça söylemeniz gerekir. Bireysel değişkenler için bu, operatör kullanılarak yapılır. sil:

// ptr'nin daha önce yeni operatör delete ptr kullanılarak tahsis edildiğini varsayalım; // ptr ile gösterilen belleği işletim sistemine geri döndür ptr \u003d 0; // ptr'yi bir boş gösterici yapın (C ++ 11'de 0 yerine nullptr kullanın)

"Hafızayı sil" ne anlama geliyor?

Silme operatörü aslında hiçbir şeyi silmez. Daha önce işletim sistemine ayrılmış olan belleği geri döndürür. İşletim sistemi daha sonra bu belleği başka bir uygulamaya (veya aynı uygulamaya) yeniden atayabilir.

Siliyormuşuz gibi görünse de değişkenama öyle değil! Bir işaretçi değişkeni hala öncekiyle aynı kapsama sahiptir ve diğer herhangi bir değişken gibi yeni bir değer atanabilir.

Dinamik olarak ayrılmış belleğe işaret etmeyen bir işaretçiyi silmenin sorunlara yol açabileceğini unutmayın.

Sarkan işaretçiler

C ++, serbest bırakılan belleğin içeriğine veya silinen işaretçinin değerine ne olacağı konusunda hiçbir garanti vermez. Çoğu durumda, işletim sistemine döndürülen bellek daha önce sahip olduğu aynı değerleri içerecektir. kurtuluşve işaretçi sadece zaten serbest bırakılmış (silinmiş) olarak hafızayı göstermeye devam edecektir.

Serbest kalan hafızayı gösteren işaretçi denir sarkan işaretçi... Sarkan bir işaretçiyi referans almak veya kaldırmak beklenmeyen sonuçlar doğuracaktır. Aşağıdaki programı düşünün:

#Dahil etmek int main () (int * ptr \u003d new int; * ptr \u003d 8; // değeri ayrılan bellek konumuna yerleştirin delete ptr; // belleği işletim sistemine geri döndür. ptr artık sarkan bir işaretçi std :: cout<< *ptr; // разыменование висячого указателя приведет к неожиданным результатам delete ptr; // попытка освободить память снова приведет к неожиданным результатам также return 0; }

#Dahil etmek

int main ()

int * ptr \u003d yeni int; // dinamik olarak bir tamsayı değişkeni ayırın

* ptr \u003d 8; // değeri ayrılmış bellek konumuna koyun

ptr'yi sil; // belleği işletim sistemine geri itin. ptr artık sarkan bir işaretçi

std :: cout<< * ptr ; // sarkan bir göstericinin referansının kaldırılması beklenmeyen sonuçlara yol açar

ptr'yi sil; // belleği yeniden boşaltmaya çalışmak da beklenmedik sonuçlara yol açacaktır

dönüş 0;

Yukarıdaki programda, daha önce tahsis edilmiş belleğe atanan 8 değeri, serbest bırakıldıktan sonra orada kalmaya devam edebilir veya olmayabilir. Ayrıca, boşaltılan belleğin başka bir uygulamaya (veya işletim sisteminin kendi kullanımı için) önceden tahsis edilmiş olması da mümkündür ve buna erişme girişimi, işletim sisteminin programınızı otomatik olarak sonlandırmasına neden olacaktır.

Hafızayı serbest bırakma süreci aynı zamanda birkaç sarkan işaretçiler. Aşağıdaki örneği düşünün:

#Dahil etmek int main () (int * ptr \u003d new int; // dinamik olarak bir tamsayı değişkeni ayırın int * otherPtr \u003d ptr; // otherPtr artık ptr delete ptr ile aynı ayrılmış belleğe işaret ediyor; // belleği işletim sistemine geri döndür. ptr ve otherPtr artık sarkan işaretçiler ptr \u003d 0; // ptr artık nullptr // ancak otherPtr hala sarkan bir gösterici! return 0;)

#Dahil etmek

int main ()

int * ptr \u003d yeni int; // dinamik olarak bir tamsayı değişkeni ayırın

int * diğerPtr \u003d ptr; // otherPtr artık ptr ile aynı ayrılmış belleğe işaret ediyor

ptr'yi sil; // belleği işletim sistemine geri itin. ptr ve otherPtr artık sarkan işaretçiler

ptr \u003d 0; // ptr artık nullptr

// ancak, otherPtr hala sarkan bir göstericidir!

dönüş 0;

İlk olarak, birden çok göstericinin ayrılmış belleğin aynı bölümünü işaret ettiği durumlardan kaçınmaya çalışın. Bu mümkün değilse, tüm işaretçilerden hangisinin belleğe "sahip" olduğunu (ve onu silmekten sorumlu olduğunu) ve hangi işaretçilerin ona basitçe eriştiğini açıklayın.

İkincisi, bir işaretçiyi sildiğinizde ve silme işleminden hemen sonra çıkmazsa, o zaman boş bırakılmalıdır, yani. değeri 0'a ayarlayın (veya C ++ 11'de). "Sildikten hemen sonra kapsam dışına çıkmak" derken, bildirildiği bloğun en sonundaki işaretçiyi sildiğiniz anlamına gelir.

Kural: Silme işleminden hemen sonra kapsam dışına çıkmazlarsa uzak işaretçileri 0'a (veya C ++ 11'de nullptr) ayarlayın.

Operatör yeni

İşletim sisteminden bellek istendiğinde, ender durumlarda kullanılamayabilir (yani mevcut olmayabilir).

Varsayılan olarak, yeni çalışmadıysa, bellek ayrılmadı, ardından bir istisna atıldı bad_alloc ... Bu istisna yanlış bir şekilde ele alınırsa (ki bu tam olarak ne olacaktır, çünkü istisnaları ve bunların işlenmesini henüz dikkate almadığımız için), o zaman program işlenmemiş bir istisna hatasıyla basitçe sonlandırılacaktır (çökecektir).

Çoğu durumda, yeni işleçle bir istisna atma işlemi (ve ayrıca bir program çökmesi) istenmez, bu nedenle, bellek ayrılamıyorsa bir boş gösterici döndüren yeni bir alternatif form vardır. Yeni anahtar kelime ile veri seçim türü arasına std :: nothrow sabitini eklemeniz yeterlidir:

int * değer \u003d new (std :: nothrow) int; // bir tamsayı değişkeninin dinamik ayrılması başarısız olursa değer işaretçisi boş olacaktır

Yukarıdaki örnekte, new dinamik olarak ayrılmış belleğe sahip bir işaretçi döndürmezse, bir boş gösterici döndürülür.

Ayrıca, beklenmedik sonuçlara (büyük olasılıkla programın çökmesine) yol açacağından, başvurunun kaldırılması önerilmez. Bu nedenle, bu isteklerin başarılı olduğundan ve belleğin ayrıldığından emin olmak için tüm bellek ayırma isteklerini kontrol etmek en iyi uygulamadır.

int * değer \u003d new (std :: nothrow) int; // eğer (! değer) ise bir tamsayı değeri için dinamik bellek ayırma isteği // yeni null döndürdüğünde durumu işle (yani bellek ayrılmamışsa) (// bu durumu işle std :: cout<< "Could not allocate memory"; }

Yeni operatörle bellek ayırmamak çok nadiren gerçekleştiği için, programcılar genellikle bu kontrolü yapmayı unuturlar!

Boş işaretçiler ve dinamik bellek ayırma

Boş işaretçiler (0 veya nullptr değerine sahip işaretçiler) özellikle yığın ayırma işleminde kullanışlıdır. Varlıkları, olduğu gibi, "bu işaretçiye hiçbir bellek ayrılmadı" diyor. Ve bu da koşullu bellek tahsisini gerçekleştirmek için kullanılabilir:

// ptr henüz bellek ayırmadıysa, onu ayırın if (! ptr) ptr \u003d new int;

Boş göstericinin kaldırılması hiçbir şeyi etkilemez. Bu nedenle, aşağıdakiler gereksizdir:

eğer (ptr) ptr'yi sil;

eğer (ptr)

ptr'yi sil;

Bunun yerine, basitçe yazabilirsiniz:

ptr'yi sil;

Ptr boş değilse, dinamik olarak ayrılan değişken kaldırılacaktır. İşaretçi değeri sıfırsa, hiçbir şey olmayacaktır.

Bellek sızıntısı

Dinamik olarak ayrılmış bellek kapsamlı değildir. Yani, açıkça serbest bırakılıncaya kadar veya programınız çıkıncaya kadar (ve işletim sistemi tüm bellek arabelleklerini kendi kendine temizleyene) kadar tahsis edilmiş olarak kalır. Bununla birlikte, dinamik olarak ayrılmış bellek adreslerini depolamak için kullanılan işaretçiler, normal değişkenlerin kapsam kurallarını izler. Bu tutarsızlık ilginç davranışlara neden olabilir.

Aşağıdaki işlevi düşünün:

void doSomething () (int * ptr \u003d yeni int;)

15.8. Yeni ve silinen operatörler

Varsayılan olarak, bir sınıf nesnesinin öbekten tahsisi ve onun tarafından kullanılan belleğin serbest bırakılması, C ++ standart kitaplığında tanımlanan new () ve delete () genel işleçleri kullanılarak gerçekleştirilir. (Bu işleçleri Bölüm 8.4'te tartıştık.) Ancak bir sınıf, aynı adı taşıyan üye işleçler sağlayarak kendi bellek yönetimi stratejisini uygulayabilir. Bir sınıfta tanımlanmışlarsa, bu sınıftaki nesneler için bellek ayırmak ve serbest bırakmak için global operatörler yerine çağrılırlar.

Screen sınıfımızda new () ve delete () operatörlerini tanımlayalım.

New () üye operatörü void * türünde bir değer döndürmeli ve ilk parametresi olarak size_t türünde bir değer almalıdır; burada size_t, sistem başlık dosyasında tanımlanan bir typedef'dir. İşte duyurusu:

void * operatörü new (size_t);

New (), bir sınıf türünde bir nesne oluşturmak için kullanıldığında, derleyici bu sınıfta böyle bir operatörün tanımlanıp tanımlanmadığını kontrol eder. Eğer öyleyse, nesne için bellek ayırmak için çağrılır, aksi takdirde - global operatör new (). Örneğin, aşağıdaki ifade

Ekran * ps \u003d yeni Ekran;

bir yığın içinde bir Screen nesnesi oluşturur ve bu sınıfın yeni bir () operatörü olduğu için çağrılır. Operatörün size_t parametresi, bayt cinsinden Ekran boyutuna eşit bir değerle otomatik olarak başlatılır.

Bir sınıfa yeni bir () operatörü eklemek veya oradan kaldırmak kullanıcı koduna yansıtılmaz. Yeniye çağrı, hem global operatör hem de üye operatör için aynı görünür. Screen sınıfının kendi new () öğesi yoksa, çağrı doğru kalır, üye operatörü yerine yalnızca global operatör çağrılır.

Genel kapsam çözünürlük operatörü, Screen sınıfı kendi sürümünü tanımlasa bile global new () 'i çağırabilir:

Ekran * ps \u003d :: new Screen;

void operatörü silme (void *);

Silme işleneni, bir sınıf türündeki bir nesneye işaretçi olduğunda, derleyici, delete () operatörünün bu sınıfta tanımlanıp tanımlanmadığını kontrol eder. Eğer evet ise, o zaman hafızayı boşaltmaya çağrılan kişi odur, aksi halde operatörün global versiyonu. Sonraki talimat

ps ile gösterilen Screen nesnesi tarafından kullanılan belleği serbest bırakır. Screen'de delete () üye operatörü olduğundan, geçerli olan budur. Void * türünde bir operatör parametresi otomatik olarak ps olarak başlatılır. Sınıfa delete () eklemek veya onu oradan kaldırmak kullanıcı kodunu hiçbir şekilde etkilemez. Silme çağrısı, hem genel operatör hem de üye operatör için aynı görünür. Screen sınıfının kendi delete () operatörü yoksa, çağrı doğru kalır, üye operatörü yerine yalnızca global operatör çağrılır.

Global kapsam çözünürlük operatörünü kullanarak, Screen kendi sürümünü tanımlasa bile global delete () çağırabilirsiniz:

Genel olarak, kullanılan delete () operatörü, belleğin tahsis edildiği new () operatörüyle eşleşmelidir. Örneğin, ps, global new () tarafından ayrılmış bir bellek alanına işaret ediyorsa, onu boşaltmak için global delete () kullanın.

Bir sınıf türü için tanımlanan delete () operatörü, bir yerine iki parametre içerebilir. İlk parametre yine void * türünde olmalı ve ikincisi önceden tanımlanmış size_t türünde olmalıdır (başlık dosyasını eklemeyi unutmayın):

// değiştirir

// void operatörü silme (void *);

İkinci bir parametre varsa, derleyici onu otomatik olarak bayt cinsinden birinci parametre tarafından adreslenen nesnenin boyutuna eşit bir değerle başlatır. (Bu parametre, delete () operatörünün türetilmiş bir sınıf tarafından miras alınabildiği bir sınıf hiyerarşisinde önemlidir. Kalıtımla ilgili daha fazla bilgi Bölüm 17'de tartışılmıştır.)

Screen sınıfındaki new () ve delete () operatörlerinin uygulanmasını daha ayrıntılı olarak ele alalım. Bellek ayırma stratejimiz, freeStore üyesi tarafından işaret edilen bağlantılı bir Ekran nesneleri listesine dayalı olacaktır. New () üye operatörü her çağrıldığında, listedeki bir sonraki nesne döndürülür. Delete () öğesini çağırdığınızda, nesne listeye döndürülür. Yeni bir nesne oluştururken, freeStore'a adreslenen liste boşsa, Screen sınıfının screenChunk nesnelerini depolamak için yeterli bir bellek bloğu elde etmek için global operatör new () çağrılır.

Hem screenChunk hem de freeStore, Screen için sadece ilgi çekicidir, bu yüzden onları özel üyeler yapacağız. Ayrıca sınıfımızın oluşturduğu tüm nesneler için bu üyelerin değerleri aynı olmalı ve bu nedenle statik olarak bildirilmelidirler. Bağlantılı bir Ekran nesneleri listesinin yapısını korumak için üçüncü bir üyeye ihtiyacımız var, sonra:

void * operatörü new (size_t);

void operatörü silme (void *, size_t);

statik Ekran * freeStore;

statik sabit int screenChunk;

Aşağıda, Screen sınıfı için new () operatörünün olası bir uygulaması verilmiştir:

#include "Screen.h"

#include cstddef

// statik üyeler başlatılır

// başlık dosyalarında değil, programın kaynak dosyalarında

Ekran * Ekran :: freeStore \u003d 0;

const int Screen :: screenChunk \u003d 24;

void * Screen :: operator new (size_t size)

eğer (! freeStore) (

// bağlantılı liste boş: yeni bir blok al

// global operatör new denir

size_t chunk \u003d screenChunk * boyut;

reinterpret_cast Screen * (yeni karakter [yığın]);

// ortaya çıkan bloğu listeye dahil et

p! \u003d & freeStore [screenChunk - 1];

freeStore \u003d freeStore-sonraki;

Ve işte delete () operatörünün uygulaması:

void Screen :: operatör silme (void * p, size_t)

// "silinmiş" nesneyi geri ekleyin,

// ücretsiz listeye

(static_cast Ekranı * (p)) - sonraki \u003d freeStore;

freeStore \u003d static_cast Ekranı * (p);

New () operatörü, ilgili delete () olmadan sınıfta bildirilebilir. Bu durumda, nesneler aynı isimdeki global operatör kullanılarak serbest bırakılır. Ayrıca, new () olmadan delete () operatörünün bildirilmesine de izin verilir: nesneler aynı ada sahip global operatör kullanılarak oluşturulacaktır. Bununla birlikte, bu operatörler genellikle yukarıdaki örnekte olduğu gibi aynı anda uygulanır, çünkü sınıfın geliştiricisi genellikle her ikisine de ihtiyaç duyar.

Programcı bunları açıkça bildirmese ve bu tür üye işlevler için olağan kısıtlamalara uymasa bile bunlar sınıfın statik üyeleridir: bu işaretçiyi geçmezler ve bu nedenle yalnızca statik üyelere doğrudan erişebilirler. (Bölüm 13.5'teki statik üye işlevler tartışmasına bakın.) Bu işleçlerin statik yapılmasının nedeni, sınıf nesnesi oluşturulmadan önce (new ()) veya yok edildikten sonra (delete ()) çağrılmalarıdır.

New () operatörünü kullanarak bellek ayırma, örneğin:

Ekran * ptr \u003d yeni Ekran (10, 20);

// C ++ 'da sözde kod

ptr \u003d Ekran :: operatör yeni (sizeof (Ekran));

Ekran :: Ekran (ptr, 10, 20);

Diğer bir deyişle, nesneye bellek ayırmak için önce sınıfta tanımlanan new () operatörü çağrılır ve daha sonra bu nesne, kurucu tarafından başlatılır. New () başarısız olursa, bad_alloc türünde bir istisna ortaya çıkar ve kurucu çağrılmaz.

Delete () operatörünü kullanarak belleği boşaltma, örneğin:

aşağıdaki gibi sırayla çalıştırmaya eşdeğerdir:

// C ++ 'da sözde kod

Ekran :: ~ Ekran (ptr);

Ekran :: operatör silme (ptr, sizeof (* ptr));

Böylece, nesne yok edildiğinde, önce sınıfın yıkıcısı çağrılır ve daha sonra hafızayı boşaltmak için delete () sınıfında tanımlanan operatör çağrılır. Ptr 0 ise, ne yıkıcı ne de delete () çağrılır.

15.8.1. Yeni ve silinen operatörler

Önceki alt bölümde tanımlanan new () operatörü, yalnızca bellek tek bir nesne için ayrıldığında çağrılır. Bu nedenle, bu talimat Screen sınıfının new () öğesini çağırır:

Ekran * ps \u003d yeni Ekran (24, 80);

global operatörün altındayken new (), bir Screen nesnesi dizisi için yığından bellek ayırmak için çağrılır:

// Screen :: operatörü new () çağrılır

Ekran * psa \u003d yeni Ekran;

Sınıfta, dizilerle çalışmak için new () ve delete () operatörlerini de bildirebilirsiniz.

New () üye operatörü bir void * değeri döndürmeli ve ilk parametresi olarak size_t değerini almalıdır. İşte Screen için yaptığı duyuru:

void * operatörü new (size_t);

Bir sınıf türünde bir nesne dizisi oluşturmak için new kullanırken, derleyici sınıfta new () operatörünün tanımlanıp tanımlanmadığını kontrol eder. Eğer öyleyse, dizi için bellek ayırmak için çağrılır, aksi takdirde - global new (). Aşağıdaki talimat, bir yığın içinde on Screen nesnesi dizisi oluşturur:

Ekran * ps \u003d yeni Ekran;

Bu sınıfın yeni bir () operatörü vardır, bu nedenle bellek ayırmak için çağrılır. Size_t parametresi, on Ekran nesnesini barındırmak için gereken bellek miktarına bayt cinsinden otomatik olarak başlatılır.

Sınıfın new () üye operatörü olsa bile, programcı genel kapsam çözüm operatörünü kullanarak diziyi oluşturmak için global new () 'i çağırabilir:

Ekran * ps \u003d :: new Screen;

Sınıfın bir üyesi olan delete () operatörü void tipinde olmalı ve ilk parametre olarak void * almalıdır. Ekran için reklamı şöyle görünüyor:

void operatörü silme (void *);

Bir dizi sınıf nesnesini silmek için silme işlemi şu şekilde çağrılmalıdır:

Silme işleneni, sınıf türündeki bir nesneye işaretçi olduğunda, derleyici, bu sınıfta delete () operatörünün tanımlanıp tanımlanmadığını kontrol eder. Cevabınız evet ise, o zaman hafızayı boşaltmaya çağrılan odur, aksi takdirde küresel versiyonu. Void * türünde bir parametre, dizinin bulunduğu bellek alanının başlangıcındaki adresin değeriyle otomatik olarak başlatılır.

Sınıfın bir delete () üye işleci olsa bile, programcı genel kapsam çözümleme işlecini kullanarak genel delete () işlevini çağırabilir:

Bir sınıfa yeni () veya delete () operatörlerinin eklenmesi veya oradan kaldırılması kullanıcı koduna yansıtılmaz: hem global operatörlere hem de üye operatörlere yapılan çağrılar aynı görünür.

Bir dizi oluştururken, gerekli belleği ayırmak için önce new () çağrılır ve ardından her öğe varsayılan kurucu kullanılarak başlatılır. Bir sınıfın en az bir kurucusu varsa, ancak varsayılan kurucusu yoksa, new () operatörünün çağrılması bir hata olarak kabul edilir. Bu şekilde bir dizi oluştururken dizi öğesi başlatıcılarını veya sınıf yapıcı bağımsız değişkenlerini belirtmek için bir sözdizimi yoktur.

Bir dizi yok edildiğinde, önce öğeleri yok etmek için sınıfın yıkıcısı çağrılır ve ardından tüm belleği boşaltmak için delete () operatörü çağrılır. Bunu yaparken doğru sözdizimini kullanmak önemlidir. Talimatlar

ps bir sınıf nesneleri dizisini gösterir, köşeli parantezlerin olmaması, bellek tamamen serbest kalsa da yıkıcının yalnızca ilk öğe için çağrılmasına neden olur.

Delete () üye operatörü bir değil iki parametreye sahip olabilirken, ikincisi size_t türünde olmalıdır:

// değiştirir

// void operatörü silme (void *);

void operatörü silme (void *, size_t);

İkinci parametre mevcutsa, derleyici onu otomatik olarak bayt cinsinden dizi için ayrılan bellek miktarına eşit bir değerle başlatacaktır.

C ++ Referans Kılavuzu kitabından yazar Stroustrap Bjarn

R.5.3.4 Silme işlemi Silme işlemi new.release-expression tarafından oluşturulan bir nesneyi yok eder: :: opt delete cast-expression :: opt delete-cast-expression Sonuç void tipindedir. Silme işleneni, yeni döndüren bir işaretçi olmalıdır. Silme işlemini uygulamanın etkisi

Microsoft Visual C ++ ve MFC kitabından. Windows 95 ve Windows NT için Programlama yazar Frolov Alexander Vyacheslavovich

Yeni ve silme işleçleri new işleci, belirtilen türde bir nesne oluşturur. Bunu yaparken, nesneyi saklamak için gereken belleği tahsis eder ve ona işaret eden bir işaretçi döndürür. Herhangi bir nedenle bellek elde edilemezse, operatör boş bir değer döndürür. Şebeke

C ++ 'ı Etkili Kullanan kitaptan Programlarınızın yapısını ve kodunu iyileştirmenin 55 kesin yolu ile Meyers Scott

Kural 16: Aynı formları yeni kullanın ve silin Aşağıdaki parçadaki sorun nedir? Std :: string * stringArray \u003d new std :: string; ... delete stringArray; İlk bakışta her şey yolunda - yeni karşılık kullanmak silin, ancak burada bir şeyler tamamen yanlış. Program davranışı

Windows 2000 / XP için Windows Script Host kitabından yazar Popov Andrey Vladimirovich

Bölüm 8 Yeniyi ve silmeyi özelleştirme Bilgi işlem ortamlarının çöp toplama için yerleşik desteğe sahip olduğu bu günlerde (Java ve .NET gibi), C ++ 'ın bellek yönetimine yönelik manuel yaklaşımı biraz modası geçmiş görünebilir. Ancak, talepkar yaratan birçok geliştirici

C ++ Programlama Standartları kitabından. 101 kural ve öneri yazar Alexandrescu Andrey

Silme Yöntemi Zorlama yanlışsa veya belirtilmezse, Silme yöntemi salt okunur bir dizini silemez. Kuvvetin true olarak ayarlanması, bu tür dizinleri hemen siler. Delete yönteminin kullanılması, belirtilen

Flash Reference kitabından yazar Yazarlar ekibi

Silme yöntemi force parametresi yanlışsa veya belirtilmezse, Delete yöntemi salt okunur bir dosyayı silemez. Kuvvetin true olarak ayarlanması, bu tür dosyaların hemen silinmesine izin verir. Not Delete yöntemi yerine DeleteFile yöntemini kullanabilirsiniz.

Firebird VERİTABANI TASARIMCI KILAVUZU kitabından Borri Helen tarafından

İlişkisel ve Boole Operatörleri İlişkisel operatörler, iki değişkenin değerlerini karşılaştırmak için kullanılır. Bu operatörler tabloda açıklanmıştır. A2.11, yalnızca doğru veya yanlış Boole değerlerini döndürebilir Tablo A2.11. İlişki Operatör Koşulu Operatörleri, için

Linux ve UNIX kitabından: Kabuk Programlama. Geliştirici kılavuzu. Teinsley David tarafından

45. new ve delete her zaman birlikte geliştirilmelidir Özet Bir sınıftaki her void * operatörü yeni (parms) aşırı yüklemesine karşılık gelen bir void operatörü silme (void *, parms) operatörü aşırı yüklemesi eşlik etmelidir; burada parms, ek parametre türlerinin bir listesidir (ilki her zaman std :: size_t'dir). Ayrıca

Yazarın kitabından SQL Referansı

delete - Bir nesneyi, dizi öğesini veya değişkeni silme delete (İşleç) Bu işleç, bir komut dosyasından bir nesneyi, nesne özelliğini, dizi öğesini veya değişkenleri silmek için kullanılır. Sözdizimi: tanımlayıcıyı silme; Bağımsız Değişkenler: Açıklama: Silme operatörü, bir nesneyi veya değişken ismi

SQL'i Anlamak kitabından Yazan Gruber Martin

DELETE deyimi DELETE deyimi, bir tablodan tüm satırları silmek için kullanılır. SQL, tek bir DELETE ifadesinin birden fazla tablodan satırları silmesine izin vermez. Yalnızca imlecin geçerli satırını değiştiren bir DELETE deyimi, konumlandırılmış silme olarak adlandırılır.

Yazarın kitabından

15.8. Yeni ve silme işleçleri Varsayılan olarak, bir sınıf nesnesinin öbekten ayrılması ve onun tarafından kullanılan belleğin serbest bırakılması, C ++ standart kitaplığında tanımlanan new () ve delete () genel işleçleri kullanılarak gerçekleştirilir. (Bu operatörleri Bölüm 8.4'te ele aldık.) Ancak bir sınıf uygulayabilir

Yazarın kitabından

15.8.1. Yeni ve silme işleçleri Önceki alt bölümde tanımlanan yeni () işleci, yalnızca bellek tek bir nesne için ayrıldığında çağrılır. Bu nedenle, bu ifadede, Screen sınıfının new () 'i: // Screen :: operator new () Screen * ps \u003d new Screen (24, 80) olarak adlandırılır;

Otomatik nesneler, derleyicide uygulanan açık kurallara göre dolaylı olarak kaldırılır. İşlev yerel değişkenleri, kontrol akışı bildirildikleri kapsamı terk ettiğinde kaldırılır. Bir sınıfın üyeleri, o sınıfın yıkıcısı çalıştırıldıktan sonra kaldırılır.

Ancak dinamik nesneler için böyle bir kural yoktur. Her zaman açıkça silinmeleri gerekir (açıkça silme, yardımcı program sınıflarının ve işlevlerinin derinliklerinde gizlenebilir). İşte daha iyi anlamak için küçük bir örnek:
struct A (std :: string str; // Yıkıcı A'da örtük olarak silinen otomatik nesne (// otomatik olarak oluşturulur). Dize arabelleğinin kendisi dinamik bir nesnedir (*), // yıkıcıda açıkça silinecektir A yıkıcıda örtük olarak çağrılacak std :: string of std :: string. // (*) String çok kısa olmadığı sürece, Small String Optimization çalışacak ve dinamik // tampon hiç ayrılmayacaktır.); void foo () (std :: vektör v; // Otomatik nesne, işlevden çıkıldığında dolaylı olarak kaldırılır. v.push_back (10); // Vektörün içeriği dinamik bir nesnedir (dizi), fonksiyondan çıkıldığında dolaylı olarak çağrılacak olan vektörün yıkıcısında // açıkça silinecektir. A a; // A sınıfının otomatik nesnesi, işlevden çıkarken örtük olarak silinir. A * pa \u003d yeni A; // pa işaretçisi otomatik bir nesnedir, işlevden çıktığında örtük olarak silinir, // ancak açıkça silinmesi gereken A sınıfı dinamik bir nesneyi işaret eder. pa'yı sil; // Dinamik nesneyi açıkça silme. auto upa \u003d // Akıllı işaretçi upa otomatik bir nesnedir, işlevden çıkıldığında örtük olarak silinir, std :: make_unique (); // ancak akıllı işaretçinin yok edicisinde // açıkça silinecek olan A sınıfı dinamik bir nesneye işaret eder. )
Dinamik nesneler genellikle öbek üzerinde bulunur, ancak bu genellikle böyle değildir. Otomatik nesneler yığın veya yığın üzerinde olabilir. Yukarıdaki örnekte, otomatik nesne upa-\u003e str yığın üzerindedir çünkü dinamik nesnenin bir parçasıdır * upa. Şunlar. dinamik / otomatik özellikler yaşam süresini tanımlar, ancak nesnenin yaşam yerini tanımlamaz.

Dinamik / otomatik özellik türe değil nesneye aittir, çünkü aynı türdeki nesneler hem dinamik hem de otomatik olabilir). Yukarıdaki örnekte, a ve * pa nesnelerinin her ikisi de A tipindedir, ancak birincisi otomatik ve ikincisi dinamiktir.

C ++ 'da dinamik nesneler yeni ile oluşturulur ve silme ile silinir. İşte tüm sorunların geldiği yer burasıdır: hiç kimse bu yapıların doğrudan kullanılması gerektiğini söylemedi! Bunlar düşük seviyeli aramalar, kaputun altında gibi görünüyorlar. Ve gereksiz yere kaputun altına girmenize gerek yok.

Dinamik nesnelere neden ihtiyaç duyulduğu hakkında biraz sonra konuşacağız.

* Dinamik / otomatik özelliği tür düzeyinde kısıtlama teknikleri vardır. Örneğin, özel kurucular.

Yeni ve sil ile ilgili sorun nedir?

Başlangıcından bu yana, yeni ve silme operatörleri gereksiz yere sıklıkla kullanılmıştır. En büyük sorunlar silme operatörüyle ilgilidir:
  • Silmeyi (bellek sızıntısı) tamamen çağırmayı unutabilirsiniz.
  • Bir istisna durumunda veya işlevden erken dönüş (ayrıca bir bellek sızıntısı) durumunda delete çağırmayı unutabilirsiniz.
  • Silme işlemini iki kez çağırabilirsiniz (çift silme).
  • Operatörün yanlış biçimini çağırabilirsiniz: silmek yerine sil veya tam tersi (tanımsız davranış).
  • Delete (sarkan işaretçi) çağrıldıktan sonra nesneyi kullanabilirsiniz.
Tüm bu durumlar en iyi ihtimalle çökmeleri programlamaya ve en kötü ihtimalle hafıza sızıntılarına ve burun arka planına yol açar.

Bu nedenle, insanlar silme operatörünü konteynerlerin ve akıllı işaretçilerin derinliklerinde saklamayı ve böylece onu istemci kodundan çıkarmayı uzun zamandır anladılar. Bununla birlikte, yeni operatörle de sorunlar var, ancak bunlar için çözümler hemen ortaya çıkmadı ve aslında, birçok geliştirici bu çözümleri kullanmaktan hala utanıyor. İşlevler yapmaya başladığımızda bunun hakkında daha fazla konuşacağız.

Şimdi yeni ve silme senaryolarına geçelim. Size birkaç senaryoya bakacağımızı ve sistematik olarak göstereceğimizi hatırlatmama izin verin, bunların çoğunda yeniyi ve sil'i kullanmayı bırakırsak kodun daha iyi olacağını göstereceğim.

Basit - dinamik dizilerden başlayalım.

Dinamik diziler

Dinamik dizi, öbek içinde ayrılmış elemanlara sahip bir dizidir. Derleme zamanında boyut bilinmiyorsa veya boyut yeterince büyükse ve diziyi genellikle boyut olarak çok sınırlı olan yığın üzerinde tahsis etmek istemiyorsak gereklidir.

Dinamik dizileri düşük seviyede tahsis etmek için C ++, yeni ve silme operatörlerinin vektör formunu sağlar: yeni ve silme. Örnek olarak, harici bir tamponla çalışan bazı işlevleri düşünün:
void DoWork (int * arabellek, size_t bufSize);
Bunun gibi işlevler genellikle saf C API'leri olan kitaplıklarda bulunur. Aşağıda, onu kullanan kodun nasıl görünebileceğine dair bir örnek verilmiştir. Bu kötü kod çünkü açıkça sil kullanır ve bununla ilgili sorunları yukarıda zaten açıkladık.
void Call (size_t n) (int * p \u003d new int [n]; DoWork (p, n); sil p; // Kötü!)
Burada her şey basit ve çoğu insan, C ++ 'da bu tür amaçlar için standart std :: vektör kabını kullanmanız gerektiğini bilir. Yapıcıda belleğin kendisini tahsis edecek ve onu yıkıcıda serbest bırakacaktır. Ek olarak, yaşamı boyunca boyutunu değiştirmeye devam edebilir, ancak bizim için artık önemli değil. Bir vektör kullanıldığında kod şöyle görünecektir:
void Call (size_t n) (std :: vector v (n); // Daha iyi. DoWork (v.data (), v.size ()); )
Böylece, silme işlevinin çağrılmasıyla ilgili tüm sorunları çözüyoruz ve ayrıca, yüzsüz bir işaretçi + sayı çifti yerine, uygun bir arayüze sahip açık bir kapsayıcıya sahibiz.

Aynı zamanda yeni ve sil yok. Bu senaryo üzerinde daha ayrıntılı durmayacağım. Deneyimlerime göre, çoğu geliştirici bu durumda ne yapacağını ve nedenini zaten biliyor.

* C ++ 'da, benzer bir arayüz, span türü kullanılarak uygulanmalıdır ... Ömürlerini etkilemeden bitişik öğe dizilerine erişmek için birleşik bir STL uyumlu arabirim sağlar (yetkin olmayan anlamlar).

** Bu makale C ++ programcıları tarafından okunduğu için, eminim ki birisi "Ha! std :: vector, en çok üç (!) işaretçi depolar, eski iyi int * tanım gereği yalnızca bir işaretçi olduğunda. Bir bellek aşımı ve bunların başlatılması için birkaç makine talimatı var! Bu kabul edilemez!". Myers, konuşmasında C ++ programcılarının bu özelliği hakkında mükemmel bir yorum yaptı. Neden Vasa Battığında C ++ Yelken Açıyor? ... Bu senin içinse gerçekten mi sorun, std :: unique_ptr önerebilirim ve gelecekte standart bize bir dynarray verebilir.

Dinamik nesneler

Dinamik nesneler, genellikle bir nesnenin yaşam süresini belirli bir kapsama bağlamak imkansız olduğunda kullanılır. Yapılabiliyorsa, kesinlikle otomatik bellek kullanmalısınız (Dinamik Nesneleri Neden Aşırı Kullanmamalısınız bölümüne bakın). Ancak bu ayrı bir makalenin konusu.

Dinamik bir nesne oluşturulduğunda, birinin onu silmesi gerekir ve koşullu olarak nesne türleri iki gruba ayrılabilir: silme işleminin farkında olmayanlar ve bir şeyden şüphelenenler. Birincisinin standart bir bellek yönetim modeline sahip olduğunu, ikincisinin ise standart olmayan bir modele sahip olduğunu varsayalım.

Standart bellek yönetimi modeline sahip türler, kapsayıcılar dahil tüm standart türleri içerir. Nitekim, konteyner kendi ayırdığı belleği yönetir. Onu kimin yarattığı ve nasıl kaldırılacağı ile hiçbir ilgisi yoktur.

Standart olmayan bir bellek yönetim modeline sahip türler, örneğin Qt nesnelerini içerir. Burada, her nesnenin onu silmekten sorumlu bir ebeveyni vardır. Ve nesne bunu biliyor çünkü QObject sınıfından miras alır. Bu aynı zamanda, örneğin boost :: intrusive_ptr ile çalışmak üzere tasarlanmış referans sayısına sahip türleri de içerir.

Başka bir deyişle, standart bir bellek yönetimi modeline sahip bir tür, ömrünü yönetmek için herhangi bir ek mekanizma sağlamaz. Bu tamamen kullanıcı tarafından yapılmalıdır. Ancak standart olmayan bir modele sahip bir tür, bu tür mekanizmalar sağlar. Örneğin, QObject, setParent () ve children () yöntemlerine sahiptir ve bir alt öğe listesi içerirken, boost :: intrusive_ptr, intrusive_ptr_add_ref ve intrusive_ptr_release işlevlerine dayanır ve bir referans sayısı içerir.

Bir nesnenin türünün standart bir bellek yönetimi modeli varsa, kısaca bunun standart bellek yönetimine sahip bir nesne olduğunu söyleyeceğiz. Benzer şekilde, bir nesnenin türü standart olmayan bir bellek yönetim modeline sahipse, bunun standart olmayan bir bellek yönetimine sahip bir nesne olduğunu söyleyeceğiz.

Sonra, her iki modelin nesnelerini ele alacağız. İleriye bakıldığında, standart bellek yönetimine sahip nesneler için, istemci kodunda kesinlikle yeni ve silinmeye değmeyeceği ve standart olmayan nesneler için belirli modele bağlı olduğu söylenmelidir.

* Bazı istisnalar: pezevenk deyimi; çok büyük bir nesne (bir bellek tamponu gibi).

** Bir istisna, std :: locale :: facet'dir (aşağıya bakın).

Standart bellek yönetimine sahip dinamik nesneler

Bunlar çoğunlukla pratikte bulunur. Ve modern C ++ 'da kullanılmaya çalışılmalıdır, çünkü özellikle akıllı işaretçilerde kullanılan standart yaklaşımlar onlarla çalışır.

Aslında akıllı işaretçiler, evet, cevap bu. Dinamik nesnelerin yaşam süreleri üzerinde kontrol verilmelidir. C ++: std :: shared_ptr ve std :: unique_ptr içinde iki tane vardır. Burada std :: poor_ptr öğesini vurgulamayacağız, çünkü sadece belirli kullanım durumlarında std :: shared_ptr için bir yardımcıdır.

Std :: auto_ptr'ye gelince, C ++ 17'den beri resmi olarak C ++ 'dan kaldırılmıştır. Huzur içinde yatsın!

Burada cihaz ve akıllı işaretçilerin kullanımı üzerinde durmayacağım, tk. bu, bu makalenin kapsamı dışındadır. Size hemen std :: make_shared ve std :: make_unique gibi harika fonksiyonlarla birlikte geldiklerini ve akıllı işaretçiler oluşturmak için kullanılmaları gerektiğini hatırlatmama izin verin.

Şunlar. bunun yerine:
std :: unique_ptr kurabiye (yeni Kurabiye (hamur, şeker, tarçın));
şöyle yazmalısın:
otomatik çerez \u003d std :: make_unique (hamur, şeker, tarçın);
Make işlevlerinin açık bir şekilde akıllı işaretçiler oluşturmaya göre avantajları, Herb Sutter tarafından GotW # 89'da ve Scott Myers tarafından Effective Modern C ++, Madde 21'de iyi belgelenmiştir. Kendimi tekrar etmeyeceğim, sadece kısa bir nokta listesi:

  • Her iki yapım işlevi için:
    • İstisnalar açısından güvenlik.
    • Yinelenen tür adı yok.
  • Std :: make_shared için:
    • Olarak performans kazancı kontrol bloğu nesnenin yanına tahsis edilir, bu da bellek yöneticisine yapılan çağrıların sayısını azaltır ve veri yerelliğini arttırır. Optimizasyon.
Yapma işlevlerinin de aynı kaynaklarda ayrıntılı olarak açıklanan bir dizi sınırlaması vardır:
  • Her iki yapım işlevi için:
    • Silicinizi aktaramazsınız. Bu oldukça mantıklı çünkü dahili olarak işlevlerin tanım gereği standart yeniyi kullanmasını sağlayın.
    • Çaprazlı başlatıcıyı veya mükemmel iletmeyle ilişkili diğer tüm özellikleri kullanamazsınız (bkz.Etkili Modern C ++, Madde 30).
  • Std :: make_shared için:
    • Uzun ömürlü zayıf referanslara sahip büyük nesneler için potansiyel bellek aşırı kullanımı (std :: thin_pointer).
    • Sınıf düzeyinde geçersiz kılınan yeni ve silme operatörleriyle ilgili sorunlar.
    • Nesne ve kontrol bloğu arasında olası yanlış paylaşım (StackOverflow'daki soruya bakın).
Uygulamada, bu sınırlamalar nadirdir ve faydaları azaltmaz. Akıllı işaretçilerin silme çağrısını bizden sakladığı ve make işlevlerinin yeni çağrıyı bizden sakladığı ortaya çıktı. Sonuç olarak, ne yeni ne de silinmiş olan daha güvenilir bir kod elde ettik.

Bu arada, Stefan Lavaye (a.k.a. STL) derslerinde makyaj işlevlerinin yapısını ciddi bir şekilde açıklıyor. Derleyiciye Yardım Etmeyin konuşmasından güzel bir slaydı aşağıda bulabilirsiniz:

Standart olmayan bellek yönetimine sahip dinamik nesneler

Akıllı işaretçiler aracılığıyla bellek yönetimine standart yaklaşıma ek olarak, başka modeller de vardır. Örneğin, referans sayma ve ebeveyn-çocuk ilişkileri.

Referans saymalı dinamik nesneler


Birçok kütüphanede kullanılan çok yaygın bir teknik. Örnek olarak OpenSceneGraph kütüphanesini alalım. C ++ ve OpenGL ile yazılmış, açık kaynak kodlu bir çapraz platform 3B motorudur.

İçindeki sınıfların çoğu, referans sayımını dahili olarak uygulayan osg :: Referenced sınıfından miras alır. Ref () yöntemi sayacı artırır, unref () yöntemi sayacı azaltır ve sayaç sıfıra düştüğünde nesneyi siler.

Kit aynı zamanda akıllı bir işaretçi ile birlikte gelir osg :: ref_ptr yapıcısında depolanan nesnede T :: ref () yöntemini ve yıkıcıda T :: unref () yöntemini çağırır. Aynı yaklaşım boost :: intrusive_ptr'de kullanılır, sadece orada ref () ve unref () yöntemleri yerine harici fonksiyonlar vardır.

Resmi OpenSceneGraph 3.0: Başlangıç \u200b\u200bkılavuzundaki kod parçacığını düşünün:
osg :: ref_ptr vertices \u003d new osg :: Vec3Array; // ... osg :: ref_ptr normaller \u003d new osg :: Vec3Array; // ... osg :: ref_ptr geom \u003d new osg :: Geometri; geom-\u003e setVertexArray (vertices.get ()); geom-\u003e
Osg :: ref_ptr gibi çok tanıdık yapılar p \u003d yeni T. Std :: make_unique ve std :: make_shared işlevlerinin std :: unique_ptr ve std :: shared_ptr sınıflarını oluşturmak için kullanıldığı gibi, osg :: make_ref işlevini yazarak osg :: ref_ptr sınıfını yaratabiliriz. Bu çok basit bir şekilde std :: make_unique işlevi ile benzer şekilde yapılır:
ad alanı osg (şablon osg :: ref_ptr make_ref (Args && ... değiştirgeler) (yeni T (std :: ileri döndür) (değiştirgeler) ...); ))
Yeni işlevimizle donanmış bu kod parçasını yeniden yazalım:
otomatik köşe noktaları \u003d osg :: make_ref (); // ... otomatik normaller \u003d osg :: make_ref (); // ... auto geom \u003d osg :: make_ref (); geom-\u003e setVertexArray (vertices.get ()); geom-\u003e setNormalArray (normalals.get ()); // ...
Değişiklikler önemsizdir ve otomatik olarak kolayca yapılabilir. Bu basit yolla, istisnalar açısından güvenlik, tip adının tekrarı yok ve standart stile mükemmel uyum sağlarız.

Delete çağrısı osg :: Referenced :: unref () metodunda zaten gizlenmişti ve şimdi yeni çağrıyı osg :: make_ref fonksiyonunda gizledik. Yani yeni ve sil yok.

* Teknik olarak, bu parçada istisnalar açısından güvenli olmayan durumlar yoktur, ancak daha karmaşık konfigürasyonlarda olabilirler.

MFC'de modsuz iletişim kutuları için dinamik nesneler


MFC kitaplığına özgü bir örneğe bakalım. Windows API üzerinden C ++ sınıflarından oluşan bir paketleyicidir. Windows için GUI'lerin geliştirilmesini basitleştirmek için kullanılır.

Microsoft'un modelsiz diyaloglar oluşturmak için resmi olarak tavsiye ettiği ilginç bir teknik. Çünkü diyalog modelsizdir, onu silmekten kimin sorumlu olduğu tam olarak belli değildir. Ona, geçersiz kılınan CDialog :: PostNcDestroy () yönteminde kendisini silmesi önerilir. Bu yöntem, pencere tarafından yaşam döngüsünde alınan son mesaj olan WM_NCDESTROY mesajı işlendikten sonra çağrılır.

Aşağıdaki örnekte, iletişim kutusu CMainFrame :: OnBnClickedCreate () yöntemindeki düğmeye tıklanarak oluşturulur ve geçersiz kılınan CMyDialog :: PostNcDestroy () yönteminde silinir.
void CMainFrame :: OnBnClickedCreate () (auto * pDialog \u003d new CMyDialog (this); pDialog-\u003e ShowWindow (SW_SHOW);) sınıf CMyDialog: public CDialog (public: CMyDialog (CWnd * pParent) (Create (IDD_MYarent_DIAL) void PostNcDestroy () geçersiz kılma (CDialog :: PostNcDestroy (); bunu silin;));
Burada ne yeni arama ne de silme çağrısı gizli değil. Kendini ayağından vurmanın birçok yolu var. İşaretçilerle ilgili olağan sorunlara ek olarak, iletişim kutusundaki PostNcDestroy () yöntemini geçersiz kılmayı unutabilirsiniz ve bir bellek sızıntısı yaşayacağız. Yeniye bir çağrı gördüğünüzde, kendinizi belirli bir anda silmek isteyebilirsiniz, iki kez siliniriz. Otomatik bellekte yanlışlıkla bir diyalog nesnesi oluşturabilirsiniz, yine çift silme işlemi alırız.

Uygulamamızdaki modelsiz diyaloglardan sorumlu olacak CModelessDialog ara sınıfı ve CreateModelessDialog fabrikasında yeniye ve silmeye çağrıları gizlemeye çalışalım:
class CModelessDialog: public CDialog (public: CModelessDialog (UINT nIDTemplate, CWnd * pParent) (Create (nIDTemplate, pParent);) korumalı: void PostNcDestroy () override (CDialog :: PostNcDestroy (); bunu sil;)); // Kalıcı iletişim kutusu şablonu oluşturma fabrikası Türetilmiş * CreateModelessDialog (Args && ... args) (// İşlev gövdesinde static_assert yerine std :: enable_if kullanabilirsiniz, bu da SFINAE'yi kullanmamıza izin verir. // Ancak bu işlevin diğer aşırı yüklemelerinden beri beklenemez, daha basit ve daha açıklayıcı bir çözüm kullanmak mantıklı görünmektedir. static_assert (std :: is_base_of :: value, "CreateModelessDialog, CModelessDialog'un soyundan gelenler için çağrılmalıdır"); auto * pDialog \u003d yeni Türetilmiş (std :: ileri (değiştirgeler) ...); pDialog-\u003e ShowWindow (SW_SHOW); dönüş pDialog; )
Sınıfın kendisi, silme işlemini gizlediğimiz PostNcDestroy () yöntemini geçersiz kılar ve torun sınıflarını oluşturmak için, içinde yeni sakladığımız bir fabrika kullanılır. Mirasçı sınıfın oluşumu ve tanımı artık şu şekildedir:
void CMainFrame :: OnBnClickedCreate () (CreateModelessDialog (bu); ) CMyDialog sınıfı: genel CModelessDialog (genel: CMyDialog (CWnd * pParent): CModelessDialog (IDD_MY_DIALOG, pParent) ());
Elbette tüm sorunları bu şekilde çözmedik. Örneğin, bir nesne yığın üzerinde hala tahsis edilebilir ve çift silme işlemi yapılabilir. Yığın üzerinde bir nesnenin tahsisini, yalnızca nesne sınıfının kendisini değiştirerek, örneğin özel bir kurucu ekleyerek yasaklamak mümkündür. Ancak bunu temel CModelessDialog sınıfından hiçbir şekilde yapamayız. Elbette, CMyDialog sınıfını tamamen gizleyebilir ve fabrikayı bir şablon değil, bazı sınıf tanımlayıcıları kabul ederek daha klasik hale getirebilirsiniz. Ancak tüm bunlar zaten makalenin kapsamı dışındadır.

Öyle ya da böyle, müşteri kodundan bir diyalog oluşturmayı ve yeni bir diyalog sınıfı yazmayı basitleştirdik. Ve aynı zamanda, yeni çağrıları kaldırdık ve çağrıları müşteri kodundan sildik.

Ebeveyn-çocuk ilişkisine sahip dinamik nesneler



Özellikle GUI geliştirme kütüphanelerinde oldukça yaygındır. Örnek olarak, iyi bilinen bir uygulama ve UI geliştirme kitaplığı olan Qt'yi düşünün.

Sınıfların çoğu QObject'ten miras alınır. Çocukların bir listesini kendi içinde tutar ve kendini sildiğinde onları siler. Ebeveyn için bir işaretçi saklar (boş olabilir) ve yaşam boyunca ebeveyni değiştirebilir.

Yenilerden ve silmeden kurtulmanın o kadar kolay olmadığı bir duruma harika bir örnek. Kütüphane, bu operatörlerin birçok durumda kullanılabileceği ve kullanılması gerektiği şekilde tasarlanmıştır. Boş olmayan bir ebeveyn ile nesneler oluşturmak için bir sarmalayıcı önerdim, ancak fikir işe yaramadı (Qt posta listesindeki tartışmaya bakın).

Bu nedenle, Qt'de yenilerden kurtulmanın ve silmenin iyi bir yolunu bilmiyorum.

Dinamik std :: locale :: faset nesneleri


C ++, akışlara veri çıkışını kontrol etmek için std :: locale nesnelerini kullanır. Yerel ayar, belirli verilerin nasıl görüntüleneceğini belirleyen bir dizi özelliktir. Fasetlerin kendi referans sayıları vardır ve yerel ayarlar kopyalanırken, hiçbir yön kopyalanmaz, sadece işaretçi kopyalanır ve referans sayısı arttırılır.

Yerel ayarın kendisi referans sayısı sıfıra düştüğünde fasetlerin kaldırılmasından sorumludur, ancak kullanıcı yeni operatörü kullanarak fasetleri oluşturmalıdır (std :: locale yapıcı açıklamasındaki Notlar bölümüne bakın):
std :: locale varsayılan; std :: locale myLocale (varsayılan, yeni std :: codecvt_utf8 );
Bu mekanizma, standart akıllı işaretçilerin tanıtılmasından önce bile uygulanmıştır ve standart kitaplıkta sınıfları kullanmak için genel kuralların dışındadır.

Yeniyi istemci kodundan çıkarmak için basit bir yerel-üreten sarmalayıcı yapılabilir. Bununla birlikte, bu genel kuralların oldukça iyi bilinen bir istisnasıdır ve bunun için bir sebze bahçesini çitlemenin bir anlamı olmayabilir.

Sonuç

Bu nedenle, önce standart bellek yönetimi ile dinamik diziler ve dinamik nesneler oluşturmak gibi senaryolara baktık. Yeni ve silme yerine standart kapsayıcılar kullandık ve işlevler yaptık ve daha basit ve daha güvenilir kod elde ettik.

Daha sonra standart olmayan bellek yönetimi örneklerine baktık ve uygun sarmalayıcılardan yeni ve silerek kodumuzu nasıl daha iyi hale getirebileceğimizi gördük. Bu yaklaşımın işe yaramadığı bir örnek de bulduk.

Bununla birlikte, çoğu durumda bu öneri mükemmel sonuçlar verir ve varsayılan bir ilke olarak kullanılabilir. Şimdi, kodda yeni veya sil kullanılıyorsa, bunun özel dikkat gerektiren özel bir durum olduğunu varsayabiliriz. Bu çağrıları müşteri kodunda görürseniz, gerçekten haklı olup olmadıklarını düşünün.

  • Kodunuzda yeni kullanmaktan ve silmekten kaçının. Bunları düşük seviyeli manuel yığın yönetimi işlemleri olarak düşünün.
  • Dinamik veri yapıları için standart kaplar kullanın.
  • Mümkün olduğunda dinamik nesneler oluşturmak için yapma işlevlerini kullanın.
  • Standart olmayan bir bellek modeline sahip nesneler için sarmalayıcılar oluşturun.

Yazardan

Şahsen, yeni ve silinin aşırı kullanımı nedeniyle birçok bellek sızıntısı ve çökme vakasıyla karşılaştım. Evet, bu kodun çoğu yıllar önce yazılmıştır, ancak daha sonra genç programcılar onunla çalışmaya başlarlar ve böyle yazılması gerektiğini düşünürler.

Umarım bu makale, yoldan çıkmaması için genç bir geliştiriciyi gönderebileceğiniz pratik bir rehber olarak ortaya çıkar.

Bir yıldan biraz daha uzun bir süre önce, C ++ Rusya konferansında bu konuyla ilgili bir sunum yaptım. Konuşmamın ardından izleyiciler iki gruba ayrıldı: Kendileri için her şeyin açık olduğu ve kendileri için harika bir keşif yapanlar. Konferanslara genellikle tecrübeli geliştiricilerin katıldığına inanıyorum, bu yüzden aralarında bu bilgilere yeni olan çok sayıda insan olsa bile, bu makalenin topluluk için yararlı olacağını umuyorum.

PS Makaleyi tartışma sürecinde meslektaşlarımla aramda ne kadar doğru "Myers" veya "Meyers" olduğu konusunda büyük bir tartışma çıktı. Bir yandan, "Meyers" Rus kulağına daha tanıdık geliyor ve biz de bunu hep söylemişiz gibi görünüyor. Öte yandan, wikide kullanılan Myers'dır. Yerelleştirilmiş kitaplara bakarsanız, genellikle o kadar çok kişi vardır: bu iki seçeneğe "Meyers" de eklenir. Konferanslarda farklı insanlar mevcut farklı şekillerde. Sonuçta biz bulmayı başardı Karar verdikleri kendine "Myers" adını verdi.

Bağlantılar

  1. Herb Sutter, GotW # 89 Çözüm: Akıllı İşaretçiler.
  2. Scott Meyers, Etkili Modern C ++, Madde 21, s. 139.
  3. Stephan T. Lavavej, Derleyiciye Yardım Etmeyin.
  4. Bjarne Stroustrup, C ++ Programlama Dili, 11.2.1, s. 281.
  5. C ++ hakkında Beş Popüler Efsane., Bölüm 2
  6. Mikhail Matrosov, Yeni olmadan C ++ ve silin.

Etiketler:

Etiket ekle

Yorumlar 134

Diziler ve işaretçiler aslında yakından ilişkilidir. Dizi adı sabit işaretçideğeri dizinin ilk elemanının adresidir (& arr). Bu nedenle, dizinin adı, işaretçilerle ilişkili tüm adres aritmetiği kurallarının uygulanacağı göstericinin başlatıcısı olabilir. Örnek program:
Program 11.1

#Dahil etmek ad alanı std kullanarak; int main () (const int k \u003d 10; int arr [k]; int * p \u003d dizi; // işaretçi dizinin ilk öğesini gösterir (int i \u003d 0; i< 10; i++){ *p = i; p++; // указатель указывает на следующий элемент } p = arr; // возвращаем указатель на первый элемент for (int i = 0; i < 10; i++){ cout << *p++ << " "; } cout << endl; // аналогично: for (int i = 0; i < 10; i++){ cout << *(arr + i) << " "; } cout << endl; p = arr; // выводим адреса элементов: for (int i = 0; i < 10; i++){ cout << "arr[" << i << "] => " << p++ << endl; } return 0; }

Programın çıktısı:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 arr \u003d\u003e 0xbffc8f00 arr \u003d\u003e 0xbffc8f04 arr \u003d\u003e 0xbffc8f08 arr \u003d\u003e 0xbffc8f0c arr \u003d\u003e 0xbffc8f10 arr \u003d\u003e 0xbffc8 0x18 arffr\u003e 0xbffc8f1c arr \u003d \u003e 0xbffc8f20 arr \u003d\u003e 0xbffc8f24

Arr [i] ifadesi - indekse göre bir elemana atıfta bulunmak, * (arr + i) olarak adlandırılan ifadeye karşılık gelir işaretçi uzaklığı (satır 22). Bu ifade, C ++ 'nın aslında dizi öğeleriyle nasıl çalıştığını daha iyi gösterir. Sayaç değişkeni i ilk öğeden kaç öğenin ofset olması gerektiğini gösterir... 17. satırda, dizi elemanının değeri göstericinin başvurusu kaldırıldıktan sonra verilir.

* P ++ ne anlama geliyor? * Operatörü daha düşük bir önceliğe sahipken, sonek artışı soldan sağa ilişkilidir. Bu nedenle, bu karmaşık ifade önce dolaylı adresleme (bir dizi öğesinin değerine erişerek) gerçekleştirecek ve ardından işaretçiyi artıracaktır. Aksi takdirde, bu ifade şu şekilde temsil edilebilir: cout Not... Bir dizi adına uygulanan sizeof () operatörü, dizinin tamamının (ilk öğenin değil) boyutunu döndürür.
Not... Adres operatörü (&), normal değişkenlerle aynı şekilde dizi öğeleri için kullanılır (dizi öğeleri bazen dizinli değişkenler olarak adlandırılır). Örneğin & arr. Bu nedenle, dizinin herhangi bir öğesine her zaman bir gösterici elde edebilirsiniz. Bununla birlikte, & arr işlemi (burada dizi, dizinin adıdır) tüm dizinin adresini döndürecektir ve örneğin (& arr + 1) işlemi, bir dizi boyutunda bir adım anlamına gelecektir. sonuncudan sonra bir elemana işaretçi elde etmektir.

Dizi öğeleriyle çalışırken işaretçi kullanmanın avantajları

Aynı sonuca götüren iki program örneğini düşünün: 0'dan 1999999'a kadar yeni değerler dizi öğelerine atanır ve görüntülenir.
Program 11.2

#Dahil etmek ad alanı std kullanarak; int main () (const int n \u003d 2000000; int kütle [n] (); for (int i \u003d 0; i< n; i++) { mass[i] = i; cout << mass[i]; } return 0; }

Program 11.3

#Dahil etmek ad alanı std kullanarak; int main () (const int n \u003d 2000000; int kütle [n] (); int * p \u003d kütle; for (int i \u003d 0; i< n; i++) { *p = i; cout << *p++; } return 0; }

Program 11.3, Program 11.2'den daha hızlı çalışacaktır (eleman sayısı arttıkça Program 11.3'ün verimliliği artacaktır)! Bunun nedeni, Program 11.2'nin geçerli dizi elemanının konumunu (adresini) ilkine (11.2, satır 12 ve 13) göre her seferinde yeniden hesaplamasıdır. Program 11.3'te, birinci elemanın adresine işaretçi başlatma sırasında bir kez erişilir (11.3, satır 11).

Bir dizinin sınırları dışında

C ++ 'da C dizileriyle çalışmanın önemli bir yönünü not edelim. C ++ eksik c dizisinin geçişine uygunluğun izlenmesi... T. hakkında. Dizinin sınırları içindeki öğelerin işlenme modunu gözlemleme sorumluluğu tamamen algoritmanın geliştiricisine aittir. Bir örneğe bakalım.
Program 11.4

#Dahil etmek #Dahil etmek #Dahil etmek ad alanı std kullanarak; int main () (int mas; default_random_engine rnd (zaman (0)); uniform_int_distribution < 10; i++) mas[i] = d(rnd); cout << "Элементы массива:" << endl; for (int i = 0; i < 10; i++) cout << mas[i] << endl; return 0; }

Program şöyle bir şey yazdıracaktır:

Dizi öğeleri: 21 58 38 91 23 5 38 -1219324996 -1074960992 0

Program 11.4'te kasıtlı bir hata var. Ancak derleyici bir hata bildirmez: dizide beş öğe bildirilir ve döngülerde 10 öğe olduğu varsayılır! Sonuç olarak, yalnızca beş öğe doğru bir şekilde başlatılacak (ayrıca, veri bozulması mümkündür) ve bunlar aynı zamanda "çöp" ile birlikte görüntülenecektir. C ++, begin () ve end () kitaplık işlevlerini kullanarak sınırları kontrol etme yeteneği sağlar (yineleyici başlık dosyasını eklemeniz gerekir). Programın değiştirilmesi 11.4
Program 11.5

#Dahil etmek #Dahil etmek #Dahil etmek #Dahil etmek ad alanı std kullanarak; int main () (int mas; int * ilk \u003d başlangıç \u200b\u200b(mas); int * son \u003d bitiş (mas); default_random_engine rnd (zaman (0)); uniform_int_distribution d (10, 99); while (ilk! \u003d son) (* ilk \u003d d (rnd); ilk ++;) ilk \u003d başlar (mas); cout<< "Элементы массива:" << endl; while(first != last) { cout << *first++ << " "; } return 0; }

Begin () ve end () işlevleri geri döner. Yineleyiciler kavramını daha sonra ele alacağız, ancak şimdilik ilk öğeye (ilk) ve sondan (sondan) sonraki öğeye işaretçiler gibi davrandıklarını varsayalım. Program 11.5'te, kompaktlık ve rahatlık adına, for döngüsünü while ile değiştirdik (çünkü burada artık bir sayaca ihtiyacımız olmadığından - işaretçi aritmetiğini kullanıyoruz). İki işaretçiye sahip olarak, döngüden çıkma koşulunu kolayca formüle edebiliriz, çünkü döngünün her adımında, ilk işaretçi artırılır.
Dizi öğelerini daha güvenli hale getirmenin bir başka yolu, konu () 'da bahsettiğimiz aralık tabanlı for döngüsünün kullanımına dayanır.

Yeni ve silme işlemleri

İşaretçileri tanımadan önce, değişkenler aracılığıyla değişken verileri belleğe yazmanın tek yolunu biliyordunuz. Değişken, adlandırılmış bir hafıza alanıdır. Karşılık gelen değişkenler için bellek blokları, program başlangıcı sırasında tahsis edilir ve program sona erene kadar kullanılır. İşaretçileri kullanarak, program çalışırken belirli bir tür ve boyutta adsız bellek blokları oluşturabilir (ve ayrıca bunları serbest bırakabilirsiniz). Bu, sınıf oluştururken en çok nesne yönelimli programlamada ortaya çıkan işaretçilerin dikkate değer bir özelliğidir.
Yeni işlem kullanılarak dinamik bellek tahsisi gerçekleştirilir. Sözdizimi:

Veri türü * işaretçi_adı \u003d yeni veri türü;

Örneğin:

Int * a \u003d new int; // int türünde bir işaretçi bildirmek int * b \u003d new int (5); // İşaretçiyi başlat

İfadenin sağ tarafı, new'in int türündeki verileri depolamak için bir bellek bloğu istediğini söylüyor. Bellek bulunursa, int türünde bir işaretçi değişkenine atanan adres döndürülür. Artık dinamik olarak oluşturulan belleğe yalnızca işaretçiler kullanılarak erişilebilir! Dinamik bellek ile çalışmanın bir örneği Program 3'te gösterilmektedir.
Program 11.6

#Dahil etmek ad alanı std kullanarak; int main () (int * a \u003d new int (5); int * b \u003d new int (4); int * c \u003d new int; * c \u003d * a + * b; cout<< *c << endl; delete a; delete b; delete c; return 0; }

Tahsis edilen hafıza ile çalıştıktan sonra, silme işlemi kullanılarak serbest bırakılmalıdır (geri döndürülür, diğer veriler için kullanılabilir hale getirilir). Bellek kullanımını kontrol etmek, uygulama geliştirmenin önemli bir yönüdür. Belleğin serbest bırakılmadığı hatalar " bellek sızıntıları", bu da anormal bir program sonlandırmasına yol açabilir. Silme işlemi bir boş göstericiye (nullptr) uygulanabilir veya yeniyle yaratılabilir (yani, new ve delete çiftler halinde kullanılır).

Dinamik diziler

Dinamik dizi program çalışırken boyutu belirlenen bir dizidir. Açıkçası, bir C dizisi C ++ 'da dinamik değildir. Yani, yalnızca dizinin boyutunu belirleyebilirsiniz ve dizinin boyutunu program çalışırken değiştirmek hala imkansızdır. Gerekli boyutta bir dizi elde etmek için, yeni bir diziye bellek ayırmak ve orijinal diziden ona veri kopyalamak ve ardından orijinal dizi için önceden ayrılmış belleği boşaltmak gerekir. C ++ 'da gerçekten dinamik bir dizi, daha sonra bakacağımız bir türdür. Yeni işlem, bir dizi için bellek ayırmak için kullanılır. Bir dizi için bellek ayırmanın sözdizimi şöyledir:
işaretçi \u003d yeni tür [boyut]. Örneğin:

Int n \u003d 10; int * arr \u003d new int [n];

Hafıza, silme operatörü kullanılarak serbest bırakılır:

Arr sil;

Bu durumda dizinin boyutu belirtilmez.
Örnek program. Dinamik bir tamsayı dizisi arr1'i rastgele sayılarla doldurun. Orijinal diziyi göster. Tek sıra sayılara (1, 3, ...) sahip tüm öğeleri yeni bir dinamik tamsayı dizisi arr2'ye yeniden yazın. Arr2 dizisinin içeriğini yazdırın.
Program 11.7

#Dahil etmek #Dahil etmek #Dahil etmek ad alanı std kullanarak; int main () (int n; cout<< "n = "; cin >\u003e n; int * arr1 \u003d yeni int [n]; default_random_engine rnd (zaman (0)); uniform_int_distribution d (10, 99); for (int i \u003d 0; i< n; i++) { arr1[i] = d(rnd); cout << arr1[i] << " "; } cout << endl; int *arr2 = new int; for (int i = 0; i < n / 2; i++) { arr2[i] = arr1; cout << arr2[i] << " "; } delete arr1; delete arr2; return 0; } n = 10 73 94 17 52 11 76 22 70 57 68 94 52 76 70 68

C ++ 'da iki boyutlu bir dizinin bir dizi dizisi olduğunu biliyoruz. Bu nedenle, iki boyutlu bir dinamik dizi oluşturmak için, önceden oluşturulan dizilerin sayısını belirledikten sonra, her gelen dizi için bir döngüde bellek ayırmak gerekir. Bunun için kullanılır işaretçiye işaretçibaşka bir deyişle, bir dizi işaretçilerin açıklaması:

Int ** arr \u003d new int * [m];

burada m, bu tür dizilerin sayısıdır (iki boyutlu bir dizinin satırları).
Örnek görev. Rastgele sayılarla doldurun ve iki boyutlu bir dinamik dizinin öğelerini çıkarın.
Program 11.8

#Dahil etmek #Dahil etmek #Dahil etmek #Dahil etmek ad alanı std kullanarak; int main () (int n, m; default_random_engine rnd (zaman (0)); uniform_int_distribution d (10, 99); cout<< "Введите количество строк:" << endl; cout << "m = "; cin >\u003e m; cout<< "введите количество столбцов:" << endl; cout << "n = "; cin >\u003e n; int ** arr \u003d new int * [m]; // diziyi doldurmak: for (int i \u003d 0; i< m; i++) { arr[i] = new int[n]; for (int j = 0; j < n; j++) { arr[i][j] = d(rnd); } } // вывод массива: for (int i = 0; i < m; i++) { for (int j = 0; j < n; j++) { cout << arr[i][j] << setw(3); } cout << "\n"; } // освобождение памяти выделенной для каждой // строки: for (int i = 0; i < m; i++) delete arr[i]; // освобождение памяти выделенной под массив: delete arr; return 0; } Введите количество строк: m = 5 введите количество столбцов: n = 10 66 99 17 47 90 70 74 37 97 39 28 67 60 15 76 64 42 65 87 75 17 38 40 81 66 36 15 67 82 48 73 10 47 42 47 90 64 22 79 61 13 98 28 25 13 94 41 98 21 28

Sorular ve cevaplar
  1. İşaretçiler ve diziler arasındaki bağlantı nedir?
  2. Dizin öğelerini yinelemek için işaretçileri kullanmak, dizin işlemini kullanmaktan neden daha etkilidir?
  3. Bellek sızıntısının özü nedir?
  4. Dizi taşmalarını önlemenin yollarını listeleyin.
  5. Dinamik dizi nedir? Neden C ++ C-dizisi özünde dinamik değil?
  6. Dinamik iki boyutlu bir dizi oluşturma sürecini açıklayın
Ders sunumu
Ev ödevi

Aşağıdaki sorunu çözmek için dinamik dizilerin kullanılması: N boyutunda bir A tamsayı dizisi verildiğinde. Orijinal dizideki tüm çift sayıları yeni bir tamsayı dizisi B'ye (aynı sırayla) yeniden yazın ve ortaya çıkan dizi B'nin boyutunu ve içeriğini yazdırın.

Ders kitabı

§62 (10) §40 (11)

Edebiyat
  1. Laforêt R. Object-Oriented Programming in C ++ (4th ed.). Peter: 2004
  2. Prata, Stephen. C ++ programlama dili. Dersler ve alıştırmalar, 6. baskı: Per. İngilizceden - M .: LLC "Kimliği Williams ", 2012
  3. Lippmann B. Stanley, Josie Lajoye, Barbara E. Mu. C ++ programlama dili. Temel kurs. Ed. 5. M: LLC "I. D. Williams", 2014
  4. Ellain A. C ++. Lamerden programcıya. SPb .: Peter, 2015
  5. Schildt G. C ++: Temel Kurs, 3. baskı. M .: Williams, 2010