Yeni ve silme operatörleri hakkında her şeyi biliyor muyuz?

  • 10.05.2019
  • öğretici

Merhaba! Aşağıda tanınmış operatörler hakkında konuşacağız yeni ve silmek, daha doğrusu, kitaplarda yazılmayanlar hakkında (en azından yeni başlayanlar için kitaplarda).
Bu makaleyi yazma konusunda yaygın bir yanlış anlamadan ilham aldım. yeni ve silmek, forumlarda ve hatta bazı kitaplarda (!!!) sürekli gördüğüm.
Hepimiz gerçekten ne olduğunu biliyor muyuz? yeni ve silmek? Yoksa bildiğimizi mi sanıyoruz?
Bu makale anlamanıza yardımcı olacak (iyi bilenler eleştirebilir :) )

Not: aşağıda tartışılacak münhasıran new operatörü hakkında, yeni operatörün diğer biçimleri ve tüm silme operatörleri için aşağıda yazılanlar da doğrudur ve analoji yoluyla geçerlidir.

Öyleyse, yeni başlayanlar için kitaplarda genellikle anlatılanlarla başlayalım. yeni(metin "tavandan" alınır, ancak bir bütün olarak gerçeğe karşılık gelir):

Şebeke yeni gerekli boyuta eşit veya daha büyük bellek tahsis eder ve C dili işlevlerinin aksine, belleğin tahsis edildiği nesne(ler) için kurucu(lar)ı çağırır ... operatörü aşırı yükleyebilirsiniz (gerçekleştirmek için yazdıkları bir yere) yeni ihtiyaçlarınıza uygun.

Ve örneğin, prototipi şuna benzeyen yeni operatörün ilkel bir aşırı yüklenmesini (uygulamasını) gösterirler.
void* operatörü yeni (std::size_t size) throw (std::bad_alloc);

Dikkat etmek istedikleriniz:
1. Hiçbir yerde paylaşılmadı yeni anahtar kelime C++ dili ve operatörü yeni, her yerde onlardan tek bir varlık olarak bahsedilir.
2. Her yerde bunu yazıyorlar yeni nesne(ler) için yapıcı(lar)ı çağırır.
Hem birinci hem de ikinci yaygın bir yanlış anlamadır.

Ancak yeni başlayanlar için kitaplara güvenmeyelim, hadi Standarda dönelim, yani bu makalenin konusunun gerçekten ortaya çıktığı (daha doğrusu hafifçe açılmış) 5.3.4 ve 18.6.1 bölümlerine.

5.3.4
Yeni ifade, uygulandığı tür kimliğinin (8.1) veya yeni tür kimliğinin bir nesnesini oluşturmaya çalışır. /*daha fazla ilgilenmiyoruz*/
18.6.1
void* operatörü new(std::size_t size) throw(std::bad_alloc);
Etkiler: Boyut baytlarını ayırmak için yeni bir ifade (5.3.4) tarafından çağrılan ayırma işlevi.
bu boyuttaki herhangi bir nesneyi temsil etmek için uygun şekilde hizalanmış depolama /*artık ilgilenmiyoruz*/

Burada zaten görüyoruz ki ilk durumda yeni olarak anılır ifade, ve ikinci olarak ilan edilir Şebeke. Ve gerçekten 2 farklı varlık!
Bunun neden böyle olduğunu anlamaya çalışalım, bunun için kodu derledikten sonra elde edilen montajcı listelerine ihtiyacımız var. yeni. Peki, şimdi sırayla her şey hakkında.

yeni ifade gibi bir dil operatörüdür eğer, süre vb. (olmasına rağmen eğer, süre vb. hala olarak anılıyor Beyan, ancak şarkı sözlerini atın) Yani. Listede bununla karşılaşan derleyici, bu operatöre karşılık gelen belirli bir kod üretir. Aynı şekilde yeni biridir anahtar kelimeler ile ortaklığını bir kez daha teyit eden C++ dili eğer"ben miyim, için" ami vb. ANCAK operatör yeni() sırayla, bu sadece davranışı yeniden tanımlanabilen aynı adı taşıyan bir C++ işlevidir. ÖNEMLİ - operatör yeni() OLUMSUZLUK belleğin tahsis edildiği nesne(ler) için yapıcı(lar)ı çağırır. Sadece bellek ayırır doğru beden ve tüm. sish işlevlerinden farkı, bir istisna atabilmesi ve yeniden tanımlanabilmesinin yanı sıra tek bir sınıf için bir operatör yapması ve böylece onu yalnızca bu sınıf için yeniden tanımlamasıdır (gerisini kendiniz hatırlayın :)).
Fakat yeni ifade sadece nesne(ler)in kurucu(lar)ını çağırır. Ayrıca hiçbir şey çağırmadığını söylemek daha doğru olsa da, basitçe, onunla tanışırken, derleyici kurucuyu/kurucuları çağırmak için kod üretir.

Resmi tamamlamak için aşağıdaki örneği göz önünde bulundurun:

#Dahil etmek sınıf Foo (genel: Foo() ( std::cout)<< "Foo()" << std::endl; } }; int main () { Foo *bar = new Foo; }

Bu kodu çalıştırdıktan sonra beklendiği gibi "Foo()" yazdırılacaktır. Nedenini anlayalım, bunun için kolaylık olması için biraz yorumladığım montajcıya bakmanız gerekiyor.
(MSVS 2012'de kullanılan cl derleyicisi tarafından elde edilen kod, çoğunlukla gcc kullanmama rağmen, ama bu konunun dışında)
/Foo *bar = yeni Foo; 1'e basın; Foo nesne çağrı operatörü için bayt cinsinden boyut yeni (02013D4h) ; çağrı operatörü yeni pop ecx mov dword ptr ,eax ; yeni'den döndürülen işaretçiyi bar ve dword ptr ,0 cmp dword ptr ,0'a yazın; bar je main+69h (0204990h) içinde 0 kayıtlı olup olmadığını kontrol edin ; 0 ise, buradan ayrılırız (belki ana veya bazı işleyicilerden, bu durumda fark etmez) mov ecx,dword ptr ; ecx'te ayrılan belleğe bir işaretçi koyun (MSVS bunu her zaman ecx(rcx) öğesine iletir) Foo::Foo (02011DBh) çağrısı yapın; ve yapıcıyı arayın; ilgilenmiyorum artık
Hiçbir şey anlamayanlar için, burada (neredeyse) C benzeri sözde kodda olanların bir analogu (yani, onu derlemeye çalışmanıza gerek yok :))
Foo *bar = operatör yeni(1); // burada 1 gerekli boyut bar->Foo(); // yapıcıyı çağır

Yukarıdaki kod, yukarıda yazılan her şeyi doğrular, yani:
1. operatör (dil) yeni ve operatör yeni()- aynı şey DEĞİLDİR.
2. operatör yeni() Yapıcı(lar)ı ÇAĞRILAMAZ
3. yapıcıları çağırmak, kodda karşılaşan derleyiciyi oluşturur anahtar kelime "yeni"

Alt satır: Umarım bu makale, aşağıdakiler arasındaki farkı anlamanıza yardımcı olmuştur. yeni ifade ve operatör yeni() hatta birisi bilmiyorsa, (bu farkın) var olduğunu bile öğrenin.

not Şebeke silmek ve operatör silme() benzer bir fark var, bu yüzden makalenin başında onu tarif etmeyeceğimi söyledim. Sanırım şimdi açıklamasının neden mantıklı olmadığını anlıyorsunuz ve yukarıda yazılanların geçerliliğini bağımsız olarak doğrulayabilirsiniz. silmek.

Güncelleme:
Bir takma adla Habrajit kim kişisel yazışmalarda, yukarıda yazılanların özünü iyi gösteren aşağıdaki kodu önerdi.
#Dahil etmek sınıf Testi (genel: Test() ( std::cout)<< "Test::Test()" << std::endl; } void* operator new (std::size_t size) throw (std::bad_alloc) { std::cout << "Test::operator new(" << size << ")" << std::endl; return::operator new(size); } }; int main() { Test *t = new Test(); void *p = Test::operator new(100); // 100 для различия в выводе }
Bu kod aşağıdaki çıktıyı verecektir
Test::operatör yeni(1) Test::Test() Test::operatör yeni(100)
ki bu bekleniyor.

Bildiğiniz gibi, C dili, belleği dinamik olarak ayırmak ve serbest bırakmak için malloc() ve free() işlevlerini kullanır. Bununla birlikte, C++, bellek ayırma ve ayırma işlemini daha verimli ve daha basit bir şekilde gerçekleştiren iki operatör içerir. Bu operatörler yenidir ve silinir. Bunların genel formu:

değişken_işaretçi = yeni değişken_tipi;

değişken_işaretçiyi sil;

Burada değişken_işaretçi, değişken_türü türünde bir işaretçidir. Yeni operatör, değişken_tipi türünde bir değer depolamak için bellek ayırır ve adresini döndürür. Yeni ile her türlü veri yerleştirilebilir. Silme operatörü, işaretçi değişkeni tarafından gösterilen belleği serbest bırakır.

Bellek ayırma işlemi gerçekleştirilemezse, o zaman yeni operatör xalloc türünde bir istisna atar. Program bu istisnayı yakalayamazsa, yürütmeden kaldırılacaktır. Bu varsayılan davranış kısa programlar için iyi olsa da, gerçek dünya uygulamaları genellikle istisnayı yakalamanızı ve uygun şekilde işlemenizi gerektirir. Bu istisnayı izlemek için istisna.h başlık dosyasını eklemelisiniz.

Silme operatörü, yalnızca yeni operatör kullanılarak tahsis edilen belleğe işaretçiler için kullanılmalıdır. Silme operatörünü diğer adres türleri ile kullanmak ciddi sorunlara neden olabilir.

malloc()'a göre new kullanmanın birçok avantajı vardır. İlk olarak, yeni operatör gereken bellek miktarını otomatik olarak hesaplar. sizeof() operatörünü kullanmaya gerek yoktur. Daha da önemlisi, yanlışlıkla yanlış miktarda bellek ayırmanızı önler. İkinci olarak, yeni operatör otomatik olarak gerekli türe bir işaretçi döndürür, bu nedenle bir tür dönüştürme operatörüne gerek yoktur. Üçüncüsü, kısaca açıklanacağı gibi, new operatörünü kullanarak bir nesneyi başlatmak mümkündür. Son olarak, yeni operatörü ve silme operatörünü global olarak veya oluşturulmakta olan sınıfa göre aşırı yüklemek mümkündür.

Aşağıda yeni ve silme operatörlerini kullanmanın basit bir örneği verilmiştir. Bellek ayırma hatalarını izlemek için bir dene/yakala bloğunun kullanımına dikkat edin.

#Dahil etmek
#Dahil etmek
int ana()
{
int*p;
denemek(
p = yeni int; // int için bellek ayırma
) yakalama (xalloc xa) (
cout<< "Allocation failure.\n";
dönüş 1;
}
*p = 20; // bu hafıza konumuna 20 değerini atamak
cout<< *р; // демонстрация работы путем вывода значения
p'yi sil; // belleği serbest bırak
0 döndür;
}

Bu program, p değişkenini bir tamsayı tutacak kadar büyük bir bellek bloğunun adresine ayarlar. Daha sonra bu belleğe bir değer atanır ve belleğin içeriği ekranda görüntülenir. Son olarak, dinamik olarak ayrılmış bellek serbest bırakılır.

Belirtildiği gibi, yeni operatörü kullanarak belleği başlatabilirsiniz. Bunu yapmak için, tür adından sonra parantez içinde başlatma değerini belirtmelisiniz. Örneğin, aşağıdaki örnekte, p ile gösterilen bellek 99 olarak başlatılmıştır:

#Dahil etmek
#Dahil etmek
int ana()
{
int*p;
denemek(
p = yeni int(99); // 99'un başlatılması
) yakalama (xalloc xa) (
cout<< "Allocation failure.\n";
dönüş 1;
}
cout<< *p;
sil;
0 döndür;
}

Diziler yeni ile tahsis edilebilir. Tek boyutlu bir dizinin genel formu:

işaretçi değişkeni = yeni değişken_tipi [boyut];

Burada boyut, dizideki öğelerin sayısını belirtir. Bir dizi tahsis ederken hatırlanması gereken önemli bir sınırlama, onun başlatılamamasıdır.

Dinamik olarak ayrılmış bir diziyi serbest bırakmak için, silme operatörünün aşağıdaki biçimini kullanmanız gerekir:

değişken_işaretçiyi sil;

Burada parantezler silme operatörüne dizi için ayrılan belleğin serbest bırakılması gerektiğini bildirir.

Aşağıdaki program, 10 yüzer bir dizi için bellek ayırır. Dizinin öğelerine 100 ile 109 arasında değerler atanır ve ardından dizinin içeriği görüntülenir:

#Dahil etmek
#Dahil etmek
int ana()
{
yüzer *p;
int ben;
denemek(
p = yeni şamandıra; // dizinin onuncu elemanını almak
) yakalama(xalloc xa) (
cout<< "Allocation failure.\n";
dönüş 1;
}
// 100'den 109'a kadar değerler atayın
için (i=0; ben<10; i + +) p[i] = 100.00 + i;
// dizinin içeriğini göster
için (i=0; ben<10; i++) cout << p[i] << " ";
sil; // tüm diziyi silme
0 döndür;
}

Diziler ve işaretçiler aslında yakından ilişkilidir. Dizi adı sabit işaretçi değeri dizinin (&arr) ilk öğesinin adresi olan . Bu nedenle, bir dizi adı, işaretçilerle ilişkili tüm adres aritmetiği kurallarının uygulanacağı bir işaretçi başlatıcı olabilir. Program örneği:
Program 11.1

#Dahil etmek ad alanı std kullanarak; int main() ( const int k = 10; int dizi[k]; int *p = dizi; // işaretçi (int i = 0; i) için dizinin ilk öğesini gösterir< 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 çıktısı:

0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 arr => 0xbffc8f00 arr => 0xbffc8f04 arr => 0xbffc8f08 arr => 0xbffc8f0c arr => 0xbffc8f10 arr => 0xbffcffc8f01 arr > 0fx 14 arr => 0xbffc8f20 arr => 0xbffc8f24

arr[i] ifadesi – dizine göre bir öğeye atıfta bulunmak, *(arr + i) ifadesine karşılık gelir ve bu ifade şu şekilde adlandırılır: ofset işaretçisi(satır 22). Bu ifade, C++'ın dizi öğeleriyle gerçekte nasıl çalıştığını daha açık bir şekilde gösterir. sayaç değişkeni i ilk elemandan kaç elemanın taşınacağını belirtir. Satır 17, işaretçinin başvurusunu kaldırdıktan sonra dizi öğesinin değerini yazdırır.

*p++ ifadesi ne anlama geliyor? * operatörü daha düşük önceliğe sahiptir, sonek artışı ise soldan sağa ilişkiseldir. Bu nedenle, bu karmaşık ifadede, önce dolaylı adresleme (bir dizi öğesinin değerine erişme) gerçekleştirilecek ve ardından işaretçi artırılacaktır. Aksi takdirde, bu ifade aşağıdaki gibi gösterilebilir: cout Not. Bir dizi adına uygulanan sizeof() operatörü, tüm dizinin boyutunu döndürür (ilk öğeyi değil).
Not. Dizi öğeleri için adres operatörü (&), sıradan değişkenlerle aynı şekilde kullanılır (dizi öğeleri bazen dizinlenmiş değişkenler olarak adlandırılır). Örneğin, &arr . Bu nedenle, dizinin herhangi bir elemanına her zaman bir işaretçi alabilirsiniz. Bununla birlikte, &arr işlemi (arr dizinin adıdır) tüm dizinin adresini döndürür ve böyle bir işlem (&arr + 1) dizinin boyutunu adımlamak, yani aşağıdaki öğeye bir işaretçi almak anlamına gelir. sonuncu.

Dizi Öğeleriyle Çalışırken İşaretçileri Kullanmanın Yararları

Aynı sonuca götüren iki program örneğini ele alalım: dizi elemanlarına 0'dan 1999999'a kadar yeni değerler atanır ve çıktıları gerçekleştirilir.
Program 11.2

#Dahil etmek ad alanı std kullanarak; int main() ( const int n = 2000000; int kütle[n] (); for (int i = 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 = 2000000; int kütle[n] (); int *p = kütle; for (int i = 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, dizinin geçerli öğesinin konumunu (adresini) her seferinde ilk öğeye göre yeniden hesaplamasıdır (11.2, satır 12 ve 13). Program 11.3'te, işaretçi başlatmada (11.3, satır 11) ilk elemanın adresine bir kez erişilir.

Sınır dışı dizi

C++'da C-dizileri ile çalışmanın bir önemli yönüne daha değinelim. C++'da mevcut değil C-dizi taşması ile uyumluluğun kontrolü. O. dizinin sınırları içindeki işleme elemanlarının modunu gözlemleme sorumluluğu tamamen algoritmanın geliştiricisine aittir. Bir örnek düşünün.
Program 11.4

#Dahil etmek #Dahil etmek #Dahil etmek ad alanı std kullanarak; int ana() ( int mas; default_random_engine rnd(time(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 çıktı verecektir:

Dizi elemanları: 21 58 38 91 23 5 38 -1219324996 -1074960992 0

Program 11.4 kasıtlı bir hataya sahip. Ancak derleyici bir hata bildirmez: dizide beş eleman bildirilir ve döngülerde 10 eleman olduğu varsayılır! Sonuç olarak, yalnızca beş öğe doğru şekilde başlatılacaktır (daha fazla veri bozulması mümkündür), "çö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 ana() ( int mas; int *ilk = başlangıç(mas); int *son = bitiş(mas); default_random_engine rnd(time(0)); uniform_int_distribution d(10, 99); while(first != last) ( *first = d(rnd); first++; ) first = start(mas); cout<< "Элементы массива:" << endl; while(first != last) { cout << *first++ << " "; } return 0; }

start() ve end() işlevleri geri döner. Yineleyici kavramını daha sonra ele alacağız, ancak şimdilik, bunların ilk öğeye (ilk) ve sondan sonraki öğeye (son) işaret eden işaretçiler gibi davrandıklarını varsayalım. Program 11.5'te, kolaylık ve kompaktlık için for döngüsünü bir süre ile değiştirdik (çünkü burada artık bir sayaca ihtiyacımız yok - işaretçi aritmetiği kullanıyoruz). İki işaretçiye sahip olarak, döngünün her adımında ilk işaretçi artırıldığından, döngüden çıkmak için kolayca bir koşul formüle edebiliriz.
Dizi öğeleri arasında geçiş yapmayı daha güvenli hale getirmenin başka bir yolu, () konusunda bahsettiğimiz aralık tabanlı for döngüsünü kullanmaktır.

yeni ve silme işlemleri

İşaretçilerle tanışana kadar, değişken verileri belleğe yazmanın tek yolu değişkenlerdi. Değişken, adlandırılmış bir bellek alanıdır. Karşılık gelen değişkenler için bellek blokları, programın başladığı anda tahsis edilir ve işi bitene kadar kullanılır. İşaretçilerin yardımıyla, programın kendisi boyunca belirli bir tür ve boyutta adsız bellek blokları oluşturabilir (ve bunları serbest bırakabilirsiniz). Bu, sınıfları oluştururken nesne yönelimli programlamada en iyi şekilde ortaya çıkan işaretçilerin harika bir özelliğidir.
Yeni işlem kullanılarak dinamik bellek tahsisi yapılır. Sözdizimi:

veri türü *işaretçi_adı = yeni veri türü;

Örneğin:

int *a = yeni int; // Tip bildirimi pointer int int *b = new int(5); // İşaretçi başlatma

İfadenin sağ tarafı, new öğesinin int türündeki verileri tutmak 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 yalnızca işaretçiler kullanarak dinamik olarak oluşturulmuş belleğe erişebilirsiniz! Program 3'te dinamik bellekle çalışmanın bir örneği gösterilmektedir.
Program 11.6

#Dahil etmek ad alanı std kullanarak; int ana() ( int *a = yeni int(5); int *b = yeni int(4); int *c = yeni int; *c = *a + *b; cout<< *c << endl; delete a; delete b; delete c; return 0; }

Ayrılan bellekle çalışma yapıldıktan sonra, silme işlemi kullanılarak serbest bırakılmalıdır (geri verilmeli, diğer veriler için kullanılabilir hale getirilmelidir). 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 programın çökmesine neden olabilir. Silme işlemi bir boş göstericiye (nullptr) uygulanabilir veya yeni ile oluşturulabilir (bu nedenle yeni ve silme çiftler halinde kullanılır).

Dinamik diziler

dinamik dizi boyutu program yürütülürken belirlenen bir dizidir. Açıkçası, bir C dizisi C++'da dinamik değildir. Yani sadece dizinin boyutunu belirleyebilirsiniz ve program çalışırken dizinin boyutunu değiştirmek yine de imkansızdır. Gerekli boyutta bir dizi elde etmek için, yeni bir dizi için bellek ayırmak ve orijinal diziden verileri bu diziye kopyalamak ve ardından orijinal dizi için daha önce ayrılan belleği boşaltmak gerekir. C++'daki gerçek dinamik dizi, daha sonra bakacağımız türdür. Yeni işlem, bir dizi için bellek ayırmak için kullanılır. Bir dizi için bellek ayırma sözdizimi şöyledir:
işaretçi = yeni tür[boyut] . Örneğin:

int n = 10; int *dizi = yeni int[n];

Silme operatörü kullanılarak bellek boşaltılır:

Diziyi sil;

Dizinin boyutu belirtilmemiş.
Program örneği. arr1 dinamik tamsayı dizisini rastgele sayılarla doldurun. Kaynak diziyi göster. Tek sıra sayılarına (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 ana() ( int n; cout<< "n = "; cin >>n; int *dizi1 = yeni int[n]; default_random_engine rnd(zaman(0)); uniform_int_distribution d(10, 99); for (int ben = 0; ben< 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, oluşturulacak dizilerin sayısını önceden belirledikten sonra, her giriş dizisi için bir döngüde bellek ayırmak gerekir. Bunun için kullanılır işaretçiden işaretçiye, başka bir deyişle, bir dizi işaretçinin açıklaması:

int **dizi = yeni int *[m];

burada m, bu tür dizilerin sayısıdır (iki boyutlu bir dizinin satırları).
Görev örneği. Rastgele sayılarla doldurun ve iki boyutlu dinamik dizinin öğelerini görüntüleyin.
Program 11.8

#Dahil etmek #Dahil etmek #Dahil etmek #Dahil etmek ad alanı std kullanarak; int ana() ( int n, m; default_random_engine rnd(time(0)); uniform_int_distribution d(10, 99); cout<< "Введите количество строк:" << endl; cout << "m = "; cin >>m; cout<< "введите количество столбцов:" << endl; cout << "n = "; cin >>n; int **dizi = yeni int *[m]; // dizi doldurma: for (int i = 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
  1. İşaretçiler ve diziler arasındaki ilişki nedir?
  2. Neden bir dizinin öğeleri üzerinde yineleme yapmak için işaretçiler kullanmak, bir dizin arama işlemi kullanmaktan daha verimlidir?
  3. "Bellek sızıntısı" kavramının anlamı nedir?
  4. Dizi taşmasını önlemenin yolları listelensin mi?
  5. Dinamik dizi nedir? Neden C++'da bir C dizisi doğası gereği dinamik değil?
  6. Dinamik iki boyutlu bir dizi oluşturma sürecini tanımlayın
Ders için sunum
Ev ödevi

Dinamik dizileri kullanarak aşağıdaki sorunu çözün: N boyutunda bir A tamsayı dizisi verildi. Orijinal dizideki tüm çift sayıları (aynı sırada) yeni bir tamsayı dizisi B'ye yeniden yazın ve elde edilen B dizisinin boyutunu ve içeriğini çıktılayın.

ders kitabı

§62(10) §40(11)

Edebiyat
  1. Laforet R. C++'da Nesne Yönelimli Programlama (4. baskı). Peter: 2004
  2. Prata, Stephen. C++ programlama dili. Dersler ve alıştırmalar, 6. baskı: Per. İngilizceden. - M.: I.D. William, 2012
  3. Lippman B. Stanley, Josy Lajoye, Barbara E. Moo. C++ programlama dili. Temel kurs. Ed. 5. M: LLC "ID Williams", 2014
  4. Elline A.C++. Lamerden programcıya. Petersburg: Peter, 2015
  5. Schildt G. C++: Temel kurs, 3. baskı. E.: Williams, 2010

15.8. Operatörler yeni ve sil

Varsayılan olarak, bir öbekten bir sınıf nesnesi tahsis etmek ve kapladığı belleği boşaltmak, C++ Standard Library'de tanımlanan global new() ve delete() operatörleri kullanılarak gerçekleştirilir. (Bu operatörleri Bölüm 8.4'te tartışmıştık.) Ancak bir sınıf aynı adı taşıyan üye operatörleri sağlayarak kendi bellek yönetim stratejisini de uygulayabilir. Bir sınıfta tanımlanmışlarsa, o sınıfın nesneleri için bellek tahsis etmek ve yeniden tahsis etmek 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'tir. İşte yaptığı duyuru:

void *operatör yeni(size_t);

Sınıf türünde bir nesne oluşturmak için new() kullanıldığında, derleyici sınıfın böyle bir operatör tanımlı olup olmadığını kontrol eder. Evet ise, nesneye bellek ayırmak için çağrılan nesnedir, aksi takdirde global operatör new() çağrılır. Örneğin, aşağıdaki ifade

Ekran *ps = yeni Ekran;

öbekte bir Screen nesnesi oluşturur ve bu sınıf bir new() operatörüne sahip olduğundan çağrılır. Operatörün size_t parametresi, Ekranın bayt cinsinden boyutuna otomatik olarak başlatılır.

Bir sınıfa new() ekleme veya çıkarma işleminin kullanıcı kodu üzerinde hiçbir etkisi yoktur. Yeni arama, hem global operatör hem de üye operatör için aynı görünüyor. Screen sınıfının kendi new()'i 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üğü operatörüyle, Screen sınıfının kendi sürümü tanımlanmış olsa bile global new() öğesini çağırabilirsiniz:

Ekran *ps = ::yeni Ekran;

void operatörü delete(void *);

Silme işleneni, bir sınıf türündeki bir nesnenin işaretçisi olduğunda, derleyici, o sınıfta delete() operatörünün tanımlanıp tanımlanmadığını kontrol eder. Eğer öyleyse, hafızayı boşaltmak için çağrılan kişi, aksi takdirde operatörün global versiyonudur. Sonraki talimat

ps ile gösterilen Screen nesnesinin kapladığı belleği serbest bırakır. Screen bir delete() üye operatörüne sahip olduğundan, geçerli olan budur. void* türünde bir operatör parametresi otomatik olarak ps olarak başlatılır. Bir sınıfa delete() eklemenin veya onu oradan kaldırmanın kullanıcı kodu üzerinde hiçbir etkisi yoktur. Silinecek ç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 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ümleme operatörüyle, Screen'in kendi sürümü tanımlanmış olsa bile global delete() işlevini çağırabilirsiniz:

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

Bir sınıf türü için tanımlanan delete() operatörü, bir yerine iki parametreye sahip olabilir. İlk parametre yine void* türünde olmalı ve ikincisi yine ö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ü delete(void *);

İkinci bir parametre varsa, derleyici bunu ilk parametre tarafından adreslenen nesnenin bayt cinsinden boyutuna eşit bir değerle otomatik olarak başlatır. (Bu seçenek, delete() operatörü türetilmiş bir sınıf tarafından miras alınabildiğinde, sınıf hiyerarşisinde önemlidir. Kalıtım hakkında daha fazla bilgi Bölüm 17'de tartışılmaktadır.)

Screen sınıfındaki new() ve delete() operatörlerinin uygulanmasına daha ayrıntılı bakalım. Bellek ayırma stratejimiz, freeStore üyesi tarafından işaret edilen bağlantılı bir Screen nesneleri listesine dayanacaktır. new() üye operatörü her çağrıldığında, listedeki bir sonraki nesne döndürülür. delete() çağrıldığında, nesne listeye döndürülür. Yeni bir nesne oluşturulduğunda, freeStore'a adreslenen liste boşsa, o zaman Screen sınıfının screenChunk nesnelerini depolamak için yeterince büyük bir bellek bloğu elde etmek için global operatör new() çağrılır.

Hem screenChunk hem de freeStore yalnızca Screen'i ilgilendiriyor, bu yüzden onları özel üyeler yapacağız. Ayrıca sınıfımızın oluşturulan tüm nesneleri için bu üyelerin değerlerinin aynı olması ve bu nedenle static olarak bildirilmesi gerekir. Screen nesnelerinin bağlantılı liste yapısını desteklemek için üçüncü bir sonraki üyeye ihtiyacımız var:

void *operatör yeni(size_t);

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

statik Ekran *freeStore;

statik const int screenChunk;

Screen sınıfı için new() operatörünün olası bir uygulaması:

#include "Ekran.h"

#include cstddef

// statik üyeler başlatıldı

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

Ekran *Ekran::freeStore = 0;

const int Ekran::screenChunk = 24;

void *Screen::operator new(size_t size)

if (!freeStore) (

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

// global operatör new çağrılır

size_t yığın = screenChunk * size;

reinterpret_cast Screen* (yeni karakter[ öbek ]);

// alınan bloğu listeye dahil et

p != &freeStore[ screenChunk - 1 ];

freeStore = freeStore-sonraki;

Ve burada delete() operatörünün uygulaması:

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

// "silinmiş" nesneyi geri ekle,

// ücretsiz listeye

(static_cast Ekran* (p))-sonraki = freeStore;

freeStore = static_cast Ekran*(p);

new() operatörü, ilgili delete() olmadan bir sınıfta bildirilebilir. Bu durumda, nesneler aynı ada sahip 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. Ancak, bir sınıfın tasarımcısı tipik olarak her ikisine de ihtiyaç duyduğundan, bu operatörler genellikle yukarıdaki örnekte olduğu gibi aynı anda uygulanır.

Programcı bunları açıkça böyle beyan etmese bile sınıfın statik üyeleridir ve bu tür üye işlevleri için olağan kısıtlamalara tabidirler: bu işaretçiden geçirilmezler ve bu nedenle yalnızca statik üyelere doğrudan erişebilirler. (Bölüm 13.5'teki statik üye işlevleri tartışmasına bakın.) Bu operatörlerin statik hale getirilmesinin nedeni, sınıf nesnesi oluşturulmadan önce (yeni()) veya yok edildikten sonra (delete()) çağrılmalarıdır.

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

Ekran *ptr = yeni Ekran(10, 20);

// C++'da sözde kod

ptr = Ekran::operatör yeni(sizeof(Ekran));

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

Başka bir deyişle, sınıfta tanımlanan new() operatörü, önce nesneye bellek ayırmak için çağrılır ve ardından bu nesne bir kurucu ile başlatılır. new() başarısız olursa, bad_alloc türünde bir istisna atılır ve yapıcı çağrılmaz.

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

aşağıdaki talimatları sırayla yürütmeye eşdeğerdir:

// C++'da sözde kod

Ekran::~Ekran(ptr);

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

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

15.8.1. Operatörler yeni ve sil

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

Ekran *ps = yeni Ekran(24, 80);

Genel operatör new(), Screen türündeki bir dizi nesne için yığından bellek ayırmak için aşağıda çağrılır:

// Screen::operator new() çağrılır

Ekran *psa = yeni Ekran;

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

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. İşte Screen için yaptığı açıklama:

void *operatör yeni(size_t);

Bir sınıf türünde bir dizi nesne oluşturmak için new kullanılırken, derleyici sınıfta new() operatörünün tanımlanıp tanımlanmadığını kontrol eder. Eğer öyleyse, diziye bellek ayırmak için çağrılan dizidir, aksi takdirde global new() çağrılır. Aşağıdaki ifade, kalçada on Screen nesnesinden oluşan bir dizi oluşturur:

Ekran *ps = yeni Ekran;

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

Sınıfın bir new() üye operatörü olsa bile, programcı global kapsam çözümleme operatörünü kullanarak bir dizi oluşturmak için global new() öğesini çağırabilir:

Ekran *ps = ::yeni Ekran;

Sınıfın bir üyesi olan delete() operatörü void türünde olmalı ve ilk parametre olarak void* almalıdır. Screen için yaptığı açıklama şöyle:

void operatörü delete(void *);

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

Silme işleneni, bir sınıf türündeki bir nesnenin işaretçisi olduğunda, derleyici, o sınıfta delete() operatörünün tanımlanıp tanımlanmadığını kontrol eder. Eğer öyleyse, hafızayı boşaltmak için çağrılan kişi, aksi takdirde küresel versiyonudur. Bir void* tipi parametre, dizinin bulunduğu bellek alanının başlangıcının adresine otomatik olarak başlatılır.

Sınıfın bir delete() üye operatörü olsa bile, programcı global kapsam çözümleme operatörünü kullanarak global delete() öğesini çağırabilir:

Bir sınıfa new() veya delete() operatörleri eklemenin veya silmenin kullanıcı kodu üzerinde hiçbir etkisi yoktur: hem global operatörlere hem de üye operatörlere yapılan çağrılar aynı görünür.

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

Bir dizi yok edildiğinde, öğeleri yok etmek için önce sınıf 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. Eğer talimatlar

ps bir dizi sınıf nesnesine işaret eder, o zaman köşeli parantezlerin olmaması, bellek tamamen serbest bırakılsa 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 olabilir, ikincisi size_t tipindedir:

// değiştirir

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

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

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

C++ Başvuru Kılavuzundan yazar Stroustrap Bjarne

R.5.3.4 Silme işlemi Silme işlemi, new.release-expression tarafından oluşturulan 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şleminin uygulanmasını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 Yeni işleç, verilen türde bir nesne oluşturur. Bunu yaparken, nesneyi depolamak için gereken belleği ayırır ve onu işaret eden bir işaretçi döndürür. Herhangi bir nedenle bellek elde edilemezse, operatör sıfır değerini döndürür. Şebeke

C++'ı Etkili Kullanma kitabından. Programlarınızın Yapısını ve Kodunu Geliştirmenin 55 Kesin Yolu tarafından Meyers Scott

Kural 16: Aynı new biçimini kullanın ve silin Aşağıdaki snippet'in nesi var? burada tamamen yanlış bir şey var. Program davranışı

Windows 2000/XP için Windows Komut Dosyası Ana Bilgisayarı kitabından yazar Popov Andrey Vladimirovich

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

C++ Programlama Standartları kitabından. 101 kural ve tavsiye yazar Alexandrescu Andrew

Silme Yöntemi Kuvvet yanlışsa veya belirtilmemişse, Sil yöntemi salt okunur bir dizini silmez. Force'u true olarak ayarlamak, bu tür dizinlerin hemen silinmesine izin verir.Delete yöntemini kullanırken, belirtilenin olup olmadığı önemli değildir.

Flash El Kitabından yazar yazarlar ekibi

Silme Yöntemi Kuvvet yanlışsa veya belirtilmemişse, Sil yöntemi salt okunur bir dosyayı silmez. Force'u true olarak ayarlamak, bu tür dosyaların hemen silinmesine olanak tanır. Not Sil yöntemi yerine DeleteFile yöntemini kullanabilirsiniz.

Firebird DATABASE GELİŞTİRİCİ KILAVUZU kitabından yazar Borri Helen

İlişkisel ve mantıksal operatörler İlişkisel operatörler, iki değişkenin değerlerini karşılaştırmak için kullanılır. Bu operatörler, Tablo'da açıklanmıştır. A2.11, yalnızca true veya false boole değerlerini döndürebilir.Tablo A2.11. İlişkisel operatörler Operatör Koşul, ne zaman

Linux ve UNIX kitabından: kabuk programlama. Geliştirici Kılavuzu. yazar Tainsley David

45. new ve delete her zaman birlikte tasarlanmalıdır Özet Bir sınıftaki void* operatörünün her new(parms) aşırı yüklemesini, buna karşılık gelen bir void operatörü delete(void* , parms) aşırı yüklemesi takip etmelidir; burada parms, ek parametrelerin bir listesidir türler (birincisi her zaman std:: size_t'dir). Aynı

Yazarın SQL Yardım kitabından

delete - Bir nesneyi, dizi öğesini veya değişkeni siler delete(Operator) Bu operatör, bir komut dosyasından bir nesneyi, nesne özelliğini, dizi öğesini veya değişkenleri kaldırmak için kullanılır.Sözdizimi: delete identifier; Bağımsız Değişkenler: Açıklama: delete operatörü yok eder bir nesne veya değişken, isim

SQL'i Anlamak kitabından Gruber Martin tarafından

DELETE deyimi DELETE deyimi bir tablodaki tüm satırları silmek için kullanılır. SQL, tek bir DELETE ifadesinin birden fazla tablodan satırları silmesine izin vermez. İmlecin yalnızca bir geçerli satırını değiştiren DELETE isteğine, konumlanmış silme adı verilir.

Yazarın kitabından

15.8. Yeni ve silme işleçleri Varsayılan olarak, bir öbekten bir sınıf nesnesi tahsis etmek ve kapladığı belleği boşaltmak, C++ Standart Kitaplığında tanımlanan global yeni() ve delete() operatörleri 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 operatörleri Önceki alt bölümde tanımlanan new() operatörü, yalnızca tek bir nesne için bellek ayrıldığında çağrılır. Bu nedenle, bu ifadede Screen:// sınıfının new() işlevine Screen::operator new()Screen *ps = new Screen(24, 80); adı verilirken, aşağıdaki şekilde çağrılır