Konuyu Oyla:
  • Derecelendirme: 0/5 - 0 oy
  • 1
  • 2
  • 3
  • 4
  • 5
İşaretçiler(Pointer) Nedir? Nasıl kullanılır?
#1
Information 
Bu yazı, C++ dilinin işaretleyicilerini anlatmak ve UE4'ün betik dilinle anlaşılması bazı örnekler verilmiştir. Java, C# gibi dillerde temel olarak işaretçiler yoktur. Bir Java yazılım dilini bilen, C++ dilini öğrenirken belki de en çok işaretçiler konusunda zorlanır. Bu yazıdan bir Alıntı yapacaksanız Ali Kubur'a teşekkürler demeniz benim için keyif olacaktır.

Öncelikle, temelini öğrenelim. Nelerde kullanacağız onları görelim. Daha sonra işaretçileri nasıl kullanacağız görelim. Sonra daha da derine inelim.  Cool Daha sonra da Unreal Engine'da <TArray> yapısına bakalım ve inceleyelim... 


İşaretçi(Pointer) Nedir?: Her bir byte, bilgisayarın hafızasında bir adresi vardır. Adresler numaralardır, mahallenizdeki evlerin kapı numaraları gibi... Programınız belleğe yüklendiğinde(çalıştırıldığında), programınızdaki her değişkenin, her fonksiyonun bellekte ayrılmış özel bir adresi vardır. 

3vWyZ5.png

Yukarıdaki örnekte gördüğünüz gibi, her bir değişkenin bellekte ayrılmış özel yeri vardır. 315,953 gibi rakamlar yerine işletim sisteminize bağlı olarak 
aşağıdaki gibi bir adres belirtir.

0x8f4ffff0 ← var1 değişkeninin adresi
0x8f4ffff4 ← var2 değişkeninin adresi
0x8f4ffff8 ← var3 değişkeninin adresi
0x8f4ffff12 ← var4 değişkeninin adresi

Peki ya neden bu adresler yapılmış? Tabii ki bir değişkenin verilerini tutmak ve o değişkenin verilerini birbirinde ayırt edebilmek için.

Diyelim ki şöyle bir programımız olsun(PHP Code dediğine bakmayın o C++ Kodu Smile):
PHP Kod:
int main(){
 
int var1 11;
 
int var2 22;
 
int var3 33;
 return 
0;


 506bd6e3403144c7bdebc078d54c312c.png
Yukarıdaki resimde gördüğünüz ff0, fff2, fff4 adreslerdir. fff0 adresi 33 değerini işaretliyor yani bu da var3'e denk geliyor. Aynı şekilde ff2 adresi 22 değerini işaretliyor ve bunun değişkeni var2... Yani değişkenler aslında adreslerin değerini tutar.  Bellekteki her adresin bir değeri vardır.

Kısacası: Aslında üzerinde anlaşıldığı gibi işaretçiler, değişkenin, fonksiyonun ya da bunun gibi bir verinin bellekteki tutulduğu yeri işaretler(tutar). 

Neden işaretçileri kullanırız? Niye buna ihtiyaç var?: 
1-Dizi Öğelerine Erişme
2-Bir fonksiyondaki değerin eşsiz olarak aktarılması
3-Dizileri ve Stringlerin fonksiyonlarını geçirmek(yani uygulamak bir dizi fonksiyonu yazacaksınız o zaman işaretçiler lazım.)
4-Sistemden hafızayı almak için
5-Bağlantılı listelerde veri yapıları oluşturmak

İşaretçileri C++'da değişkenlerde kullanmak.
Öncelikle, adresleme ve belleğin mantığını anladığımıza göre, işaretçileri nasıl kullanacağımıza bakalım.

* (asteriks sembolü) = işaretçi tanımlamak için bu sembolü kullanırız.
& (ampersant sembolü) = bir değişkenin veya fonksiyonun adresini referans olarak kullanmak için işe yarar. yani &degisken3 dediğimiz zaman aslında o değişkenin adresini alırız.

PHP Kod:
#include <iostream>
using namespace std;
int main()
{
int var1 11 // iki tane tam sayı değişkeni var
int var2 22;

cout << &var1 << endl // değişkeninin adresini yazar 000x000.. gibi bir adres değeri çıkacaktır.
<< &var2 << endl << endl;
intptr// tamsayıya bir işaretçi tanımadık
ptr = &var1// ptr adlı işaretçi var1'in adresini işaretliyor
cout << ptr << endl// işaretçinin değerini yaz.
ptr = &var2// ptr adlı işaretçi var2'in adresini işaretliyor
cout << ptr << endl// işaretçinin değerini yazdır
system("pause");
}
  
Program Çıktısı( Değerler sizin bilgisayarınız için değişecektir.)
0084F790     yani var1'in adresi

0084F784     yani var2'nin adresi

0084F790    ptr işaretçisi ile var1'in adresi
0084F784    ptr işaretçisi ile var2'in adresi



İşaretçi Tanımlanması

int* ptr;  // Burada ptr adında bir tamsayıya ait işaretçi tanımladık.

Adreslerin işaretlemesi:
ptr = &var1;  // burada ptr işaretçisini var1'in adresine işaretledik.


Tekrardan ptr adresinin işaretlemesi:

ptr = &var2;  // burada ptr işaretçisini var2'in adresine işaretledik. ptr'nin aldığı değerde değişti herhalde...

Bir başka örnek:

PHP Kod:
int main()
{
int var1var2// iki tamsayı değişkeni atadık
intptr// tamsayı  için ptr adında işaretçi belirledik
ptr = &var1// ptr işaretçisini değişken 1'in adresine işaretledik.
*ptr 37// var1=37 ile aynı şey demektir.
var2 = *ptr//var2=var1 ile aynı şey demektir
cout << var2 << endl// var2 değişkeninin 37 olacağını kontrol edelim.
return 0;




İşaretçileri kullanarak basitçe fonksiyonları uygulamak:

PHP Kod:
#include <iostream>
using namespace std;
int main()
{
void centimize(double*); //fonksiyonunun prototipi oluşturduk bu fonksiyon inç uzunluk birimini santimetreye çeviriyormuş
double var = 10.0// var değişkenine 10.0 atadık.
cout << “degisken “ << var << “ inc ” << endl;
centimize(&var); // değişkenin adresini fonksiyona uyguladık.
cout << “var “ << var << “ santimetre” << endl;
return 
0;
}
//--------------------------------------------------------------
void centimize(doubleptrd)
{
*
ptrd *= 2.54//*ptrd işaretçisi ile gelen değeri 2.54 sayısı ile çarpıp santimetreye çevirdik. ilk kullandığımız * işareti işaretçiyi diğer kullandığımız çarpmayı temsil ediyor.
}  

İşaretçilerin bir çok örneği mevcut, bu yazıda temel mantığı anladıktan sonra Unreal Engine'da kullandığım bir kod parçacağından anlatmak istiyorum.

Bir değişken tanımlıyorum.
ATriggerVolume* PressurePlate;

Bir dizi tanımlıyorum;

PHP Kod:
TArray<AActor*> OverlappingActors // burada bir OverlappingActors adında bir Aktör dizisi tanımladım ve işaretçi uyguladım.
AActorOwner// AActor sınıfından bir pointer değişkeni aldım, bu aktörü temsil ediyor. 


Aşağıdaki örnek biraz karışık gelebilir fazla takmayın. Öncelikle -> "ok" göstergeçin ne olduğunu belirtelim. Bir sınıftan bir pointere ulaşmak istiyorsanız -> kullanmanız lazım. Kullanılan döngü uzak-tabanlı "ranged-based" ve auto anahtar kelimesi değişkenin int, char, float ya da başka bir şey gibi belirtmeden otomatik olarak tanıtılmasını algılayan bir kelime. neyse bunlardan ziyade aşağıda örnekte işaretçileri (*) ve referans adreslerini(&) nasıl kullanıldığına bakın.

PHP Kod:
for (const autoActor OverlappingActors)
{
TotalMass += Actor->FindComponentByClass<UPrimitiveComponent>()->GetMass();   // kütleleri topla
UE_LOG(LogTempWarningTEXT("Total actor on plate : %s"), *Actor->GetName());  


*Actor->GetName(); burada, gördüğünüz üzere aktörü pointer ile aldığım için ve metoda erişmek için -> kullanılmış. 
Bir diğer yaptığımız bir çok şey ise nullptr yani işaretçinin değerini boşaltmak, hiçbir özellik eklememek

*AActor Lamb = nullptr; // gibisinden. eğer aktör yoksa gibisinden düşünebilirsiniz.


Ayrıca Unreal Engine API'de ne zaman * ya da ne zaman & kullanacağınızı bilebilirsiniz. Örneğin
3071cc9d703e4478a7aac00d6798ca23.png
Yukarıda gördüğünüz üzere, Append metodumuzun iki tane parametresi var.     

const ElementType * Ptr, 
int32 Count




yani



dizi->append(diger_dizi*, 5);

gibisinden.



PHP CODE yazan yerlere bakmayın, bildiğiniz gibi hepsi C++ kodu :Smile Bu konu hakkında sorularını varsa bu başlık altından sorup & tartışabilirsiniz.
Cevapla
#2
Güzel bir anlatım olmuş tebrikler. Ben de bazı noktalara değinmek istiyorum.

Pointer kavramını şöyle düşünmek gerekir. Bir pointer bir nesne yada değerin referansıdır. Bellek adresini işaret eder ama bellek adresi değildir. Pointerın değeri bellek adresidir.

Daha önce bahsetmiştim. Değişken veya sabitlerin türü (int, float, string, object, actor,...) ne olursa olsun her biri için 2 tip* vardır. Bunlar;
Referans(Reference): Özel olarak belirli bir nesneyi yada değişkeni işaret ederler. Bu o bilgisayar sisteminde benzersizdir.
Değer(Value): Bir nesnenin değerini belirtir.

(*) Burada tip sıfatını ben koydum, literatürde ne kullanılabilir belirsizdir.

Bu ne demek? Buradaki senaryoya göre referans ve değere şöyle bir örnek verelim. Örneğin minecraft oyununda yan yana duran 2 adet toprak bloğunu göz ününde bulunduralım. Bu bloklar görünüş, doku, kırılganlık...vb gibi tüm özellikler açısından birbirinin tıpatıp aynısıdır ve sayısal olarak aynı değerler ile tanımlanırlar ama gündelik hayattaki gibi de biliyoruz ki mesela bir tanesi soldaki blok bir tanesi de sağdaki bloktur. İşte bu ayrım bilgisayarda bellekteki konumu ile yani dolayısı ile referans vererek yapılabilir.

Pratikte ise şöyle bir fark oluşabilir(madem gelenek devam ediyor ben de aslında C++ olan PHP kodu ile göstereyim  Smile ).

Bu fonksiyon kendisine değer olarak verilen bir değişkeni 1 arttırır. Değiştirmeden önce ve sonra bu değişkenin değerini ekrana yazar.
PHP Kod:
/**
 * Increase the value of v passed by value.
 * @param v Value to increase value.
 */
void pass_by_value_function(int v)
{
 
cout << "pass_by_value_function:" << << endl;
 
v++;
 
cout << "pass_by_value_function:" << << endl;


Bu fonksiyon ise kendisine referans olarak verilen bir değişkeni 1 arttırır. Değiştirmeden önce ve sonra bu değişkenin değerini ekrana yazar.
PHP Kod:
/**
 * Increase the value of v passed by reference.
 * @param v Reference to increase value.
 */
void pass_by_reference_function(intv)
{
 
cout << "pass_by_reference_function:" << (*v) << endl;
 (*
v)++;
 
cout << "pass_by_reference_function:" << (*v) << endl;


Son olarak da bu fonksiyonların kullanışına bakalım.
PHP Kod:
int main()
{
 
// Create a variable 'x' and assign value 5 to it.
 
int x 5;

 
// Print value of value 'x' before pass to a function.
 
cout << "Value before pass_by_value function:" << << endl;
 
// Pass by value
 
pass_by_value_function(x);
 
// Print value of value 'x' after pass to a function.
 
cout << "Value after pass_by_value function:" << << endl << endl;

 
// Print value of 'x' before pass to a function.
 
cout << "Value before pass_by_reference function:" << << endl;
 
// Pass by reference
 
pass_by_reference_function(&x);
 
// Print value of value 'x' after pass to a function.
 
cout << "Value after pass_by_reference function:" << << endl;

 return 
0;


Konsol çıktısı:
Kod:
Value before pass_by_value function:5
pass_by_value_function:5
pass_by_value_function:6
Value after pass_by_value function:5

Value before pass_by_reference function:5
pass_by_reference_function:5
pass_by_reference_function:6
Value after pass_by_reference function:6

Burada heap/stack gibi teknik konulara girmeden belirtmek gerekirse dikkat edilmesi gereken nokta şudur. Değer olarak ifade edilen bir değişken, örneğin bir fonksiyona parametre olarak verildiğinde onun değeri fonksiyon içerisinde değiştirilse dahi fonksiyon gövdesi tamamlandığında değerinin değişmemiş olduğu görülecektir. Ancak referans olarak verildiğinde, spesifik bir nesneyi belirttiğinden değişiklik kalıcı olacaktır.

Java ve C# gibi programlama dillerinde struct yapıları değer, class yapıları ise referans olarak saklanırken; C++ dilinde böyle bir ayrım yoktur, herhangi bir yapının değer yada referans olması pointer ve address operator ile kontrol edilebilir.

Ancak UBT(Unreal Build Tool), USTRUCT ve UCLASS makroları kullanılarak oluşturulmuş olan struct ve class tiplerini tıpkı C# ve Javadaki gibi sırasıyla değer ve referans olarak saklar(generate eder). Tüm USTRUCTlar değer, tüm UCLASSlar ise reference olarak kullanılırlar.


Ek Notlar

(12-01-2017, Saat: 07:53)Khubur Adlı Kullanıcıdan Alıntı:  İşaretçi(Pointer) Nedir?: Her bir bitin(byte), bilgisayarın hafızasında bir adresi vardır.
Burayı pek anlayamadım, belki de böyle demek istemediniz ama netleştirelim. Bitlerin adresi yoktur, bellekte tek başına 1 bit oluşturamaz/saklayamazsınız; byteların adresi vardır. Bellekte tek başına saklanabilen anlamlı en küçük yapı taşı bytedır.

(12-01-2017, Saat: 07:53)Khubur Adlı Kullanıcıdan Alıntı:  Diyelim ki şöyle bir programımız olsun(PHP Code dediğine bakmayın o C++ Kodu Smile):
PHP Kod:
int main(){
 
int var1 11;
 
int var2 22;
 
int var3 33;
 return 
0;


 506bd6e3403144c7bdebc078d54c312c.png
Yukarıdaki resimde gördüğünüz ff0, fff2, fff4 adreslerdir. fff0 adresi 33 değerini işaretliyor yani bu da var3'e denk geliyor. Aynı şekilde ff2 adresi 22 değerini işaretliyor ve bunun değişkeni var2... Yani değişkenler aslında adreslerin değerini tutar.  Bellekteki her adresin bir değeri vardır.
int türü 4byte(32bit) olduğundan en azından ..ff0, ..ff4, ...ff8, ...ffc şeklinde devam etmeliydi, yani en azından 2bytetan daha büyük aralıklara sahip olmalıydı ama belki de örnek 16bit(2byte) int türünden kalma.

Son olarak C/C++ kodu yazarken tanımlanan her değişkenin null olarak bile olsa mutlaka ilklenmesini(ilk değerini atama) tavsiye ederim. Yoksa derleyiciye göre bazen tahmin edemeyeceğiniz değerler alabilir yada ilklenmemiş değişkenin kullanımı şeklinde bir çalışma sırasında(runtime error) hata ile karşılaşabilirsiniz.

Yanlış:
PHP Kod:
int main()
{
 
int v;
 
intptr;

 
v++; // Error: The variable 'v' is being used without being initialized.
 
if (ptr == nullptr// Error: The variable 'ptr' is being used without being initialized.
 
{

 }


Doğru:
PHP Kod:
int main()
{
 
int v 0;
 
intptr nullptr;

 
v++;
 if (
ptr == nullptr)
 {

 }

Cevapla
#3
Eklediğiniz ekstra bilgiler için teşekkürler @cahitburak Minecraft blok örneği çok iyi şekilde anlaşılır olmuş.
Cevapla
#4
Ali Burak'a ve Cahit Burak'a teşekkürler.
Ara
Cevapla
#5
(13-01-2017, Saat: 16:52)nurisural Adlı Kullanıcıdan Alıntı:  Ali Burak'a ve Cahit Burak'a teşekkürler.

Kardeş değiliz, benim soyadım Kubur ))
Cevapla
 


Konu ile Alakalı Benzer Konular
Konular Yazar Yorumlar Okunma Son Yorum
  [EĞİTİM] C++ Unreal Smart Pointer Libary (USPL) Kullanımı [UETR]Khubur 0 243 24-03-2017, Saat: 19:24
Son Yorum: [UETR]Khubur
  Static Library dosyası unreal engine nasıl import edilir ? Faruk 5 371 31-07-2016, Saat: 17:55
Son Yorum: Faruk
  UE4 için C++ nasıl öğrenilir? QuickPlay 3 706 12-04-2016, Saat: 20:35
Son Yorum: merbekta
  eski sürümü nasıl geri getirebilirim cetcet21 0 299 05-03-2016, Saat: 22:35
Son Yorum: cetcet21
  UObject Nasıl Replicate Edilebilir ? [UETR]cahitburak 3 411 01-03-2016, Saat: 22:55
Son Yorum: [UETR]cahitburak

Hızlı Menü:


Unreal Engine Türkiye

This forum is only for fans and support. It has nothing to do with Epic Games.

Bu site sadece fan ve destek amaçlıdır. Epic Games ile bir ilgisi yoktur.