C işaretçileri için işaretçiler nasıl çalışır? Onları ne zaman kullanırsın?
C işaretçileri için işaretçiler nasıl çalışır? Onları ne zaman kullanırsın?
Yanıtlar:
8 bit adresli bir 8 bit bilgisayar (ve sadece 256 bayt bellek) olduğunu varsayalım. Bu, belleğin bir parçasıdır (üstteki sayılar adreslerdir):
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
| | 58 | | | 63 | | 55 | | | h | e | l | l | o | \0 | |
+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+----+
Burada görebileceğiniz şey, adres 63'te "merhaba" dizesinin başlamasıdır. Yani bu durumda, eğer hafızadaki "merhaba" nın tek oluşumu buysa,
const char *c = "hello";
... c
(salt okunur) "merhaba" dizesine bir işaretçi olarak tanımlar ve bu nedenle 63 değerini içerir. c
kendisi bir yerde saklanmalıdır: yukarıdaki örnekte 58 numaralı konumda. Elbette yalnızca karakterleri gösteremeyiz , aynı zamanda diğer göstergelere de. Örneğin:
const char **cp = &c;
Şimdi cp
işaret c
olduğunu, bu adresini içerir c
(58 olan). Daha da ileri gidebiliriz. Düşünmek:
const char ***cpp = &cp;
Şimdi cpp
adresini saklar cp
. Yani 55 değerine sahiptir (yukarıdaki örneğe dayanarak) ve tahmin ettiniz: kendisi 60 adresinde saklanıyor.
Neden işaretçileri işaretçiler kullandığına gelince :
t
bir başvuruda tür vardır t *
. Şimdi bir dizi diziyi düşünün t
: doğal olarak bu 2D diziye yapılan bir başvuruda type (t *)*
= olur t **
ve dolayısıyla bir işaretçiye işaretçi olur.char **
.f
tür t **
değişkenini değiştirmesi gerekiyorsa, tür argümanını kabul etmesi gerekir t *
.C işaretçileri için işaretçiler nasıl çalışır?
İlk olarak bir işaretçi, diğer değişkenler gibi bir değişkendir, ancak değişkenin adresini tutar.
İşaretçiye işaretçi, diğer değişkenler gibi bir değişkendir, ancak değişkenin adresini tutar. Bu değişken sadece bir işaretçi olur.
Onları ne zaman kullanırsın?
Yığın üzerindeki bir belleğe bir işaretçi döndürmeniz gerektiğinde ancak dönüş değerini kullanmamanız durumunda bunları kullanabilirsiniz.
Misal:
int getValueOf5(int *p)
{
*p = 5;
return 1;//success
}
int get1024HeapMemory(int **p)
{
*p = malloc(1024);
if(*p == 0)
return -1;//error
else
return 0;//success
}
Ve buna şöyle diyorsun:
int x;
getValueOf5(&x);//I want to fill the int varaible, so I pass it's address in
//At this point x holds 5
int *p;
get1024HeapMemory(&p);//I want to fill the int* variable, so I pass it's address in
//At this point p holds a memory address where 1024 bytes of memory is allocated on the heap
Her C programının main () argümanının argv için bir işaretçiye işaretçisi olması gibi başka kullanımlar da vardır, burada her öğe komut satırı seçenekleri olan karakter dizisini içerir. Dikkatli olmalısınız, ancak 2 boyutlu dizileri işaret etmek için işaretçiler kullandığınızda, bunun yerine 2 boyutlu diziye bir işaretçi kullanmak daha iyidir.
Neden tehlikeli?
void test()
{
double **a;
int i1 = sizeof(a[0]);//i1 == 4 == sizeof(double*)
double matrix[ROWS][COLUMNS];
int i2 = sizeof(matrix[0]);//i2 == 240 == COLUMNS * sizeof(double)
}
Düzgün yapılmış 2 boyutlu bir dizi için bir işaretçi örneği:
int (*myPointerTo2DimArray)[ROWS][COLUMNS]
ROWS ve COLUMNS için değişken sayıda elemanı desteklemek istiyorsanız, 2 boyutlu bir dizi için bir işaretçi kullanamazsınız. Ama önceden bildiğiniz zaman 2 boyutlu bir dizi kullanırsınız.
Git 2.0, pointer 7b1004b , işaretçi işaretçi kullanımı bu "gerçek dünya" kod örneği gibi :
Linus bir keresinde şöyle dedi:
Aslında daha fazla insanın gerçekten temel düşük seviyeli kodlamayı anlamasını diliyorum. Kilitsiz isim araması gibi büyük, karmaşık şeyler değil, sadece işaretçilerden işaretleyicilere vb. İyi bir şekilde kullanılır.
Örneğin, "önceki" girişi izleyerek tek bağlantılı bir liste girişini silen çok fazla insan gördüm ve ardından girişi silmek için
if (prev)
prev->next = entry->next;
else
list_head = entry->next;
ve böyle bir kod gördüğümde, "Bu kişi işaretçileri anlamıyor" diye gidiyorum. Ve ne yazık ki oldukça yaygın.
İşaretçileri anlayan insanlar sadece bir " giriş işaretçisine işaretçi " kullanırlar ve bunu list_head adresiyle başlatırlar. Ve sonra listeden geçerken, herhangi bir koşul kullanmadan girdiyi kaldırabilirler.
*pp = entry->next
Bu basitleştirmeyi uygulamak, 2 yorum satırı eklerken bile bu işlevden 7 satır kaybetmemizi sağlar.
- struct combine_diff_path *p, *pprev, *ptmp;
+ struct combine_diff_path *p, **tail = &curr;
Chris işaret yorumlarında 2016 video "için Torvalds adlı Çift Pointer Sorunu tarafından" Philip Buuck .
kumar işaret yorumlarında "blog yazısı anlama İşaretçiler üzerinde Linus ," Grisha Trubetskoy açıklıyor:
Bağlantılı bir listeniz olduğunu düşünün:
typedef struct list_entry {
int val;
struct list_entry *next;
} list_entry;
Başından sonuna kadar yinelemeniz ve değeri to_remove değerine eşit olan belirli bir öğeyi kaldırmanız gerekir.
Bunu yapmanın en belirgin yolu:
list_entry *entry = head; /* assuming head exists and is the first entry of the list */
list_entry *prev = NULL;
while (entry) { /* line 4 */
if (entry->val == to_remove) /* this is the one to remove ; line 5 */
if (prev)
prev->next = entry->next; /* remove the entry ; line 7 */
else
head = entry->next; /* special case - first entry ; line 9 */
/* move on to the next entry */
prev = entry;
entry = entry->next;
}
Yukarıda yaptığımız şey:
- giriş olana kadar liste üzerinde yineleme yapmak, yani listenin
NULL
sonuna ulaştığımız anlamına gelir (satır 4).- Kaldırılmasını istediğimiz bir girişle karşılaştığımızda (satır 5),
- mevcut bir sonraki işaretçinin değerini bir öncekine atarız,
- böylece mevcut elemanın ortadan kaldırılması (hat 7).
Yukarıda özel bir durum vardır - yinelemenin başında önceki bir giriş (
prev
isNULL
) yoktur ve bu nedenle listedeki ilk girişi kaldırmak için başın kendisini değiştirmeniz gerekir (satır 9).Linus'un söylediği şey , yukarıdaki kodun, bir önceki öğeyi yalnızca bir işaretçi yerine bir işaretçi için bir işaretçi haline getirerek basitleştirilebileceğidir .
Kod daha sonra şöyle görünür:
list_entry **pp = &head; /* pointer to a pointer */
list_entry *entry = head;
while (entry) {
if (entry->val == to_remove)
*pp = entry->next;
pp = &entry->next;
entry = entry->next;
}
Yukarıdaki kod bir önceki varyanta çok benzer, ancak başlangıçta
pp
olmadığı için listenin ilk öğesinin özel durumunu nasıl izlememiz gerektiğine dikkat edinNULL
. Basit ve akıllı.Ayrıca, bu konudaki biri bunun daha iyi olmasının
*pp = entry->next
atomik olmasından kaynaklandığını söyledi . Kesinlikle atomik DEĞİLDİR .
Yukarıdaki ifade iki dereference operatörü (*
ve->
) ve bir ödev içerir ve bu üç şeyden hiçbiri atomik değildir.
Bu yaygın bir yanlış anlamadır, ancak ne yazık ki C'deki hiçbir şeyin atomik olduğu varsayılmamalıdır (++
ve--
operatörler dahil )!
Üniversitedeki bir programlama kursuna ilişkin göstergeleri ele alırken, bunları öğrenmeye nasıl başlayacağımız konusunda iki ipucu verildi. Birincisi Binky ile Pointer Fun'ı görmekti . İkinci düşünmek oldu Haddocks' Gözler Lewis Carroll gelen geçit Looking-Glass sayesinde
“Üzgünsün,” dedi Knight endişeli bir ses tonuyla: “Seni rahatlatmak için sana bir şarkı söyleyeyim.”
“Çok mu uzun?” Diye sordu Alice, çünkü o gün çok fazla şiir duymuştu.
“Uzun,” dedi Şövalye, “ama çok, çok güzel. Beni duyduğunu duyan herkes - ya gözyaşlarını gözlerine getiriyor, ya da başka - ”
"Ya da ne?" dedi Alice, Şövalye ani bir duraklama yapmıştı.
“Yoksa öyle değil, biliyorsun. Şarkının adı 'Haddocks' Eyes 'olarak adlandırılıyor. ”
“Ah, bu şarkının adı, değil mi?” Dedi Alice, ilgilenmeye çalışarak.
"Hayır, anlamıyorsun," dedi Şövalye biraz endişeli görünerek. “İsmin adı bu. Adı gerçekten 'Yaşlı Yaşlı Adam'. ”
“O zaman 'Şarkının adı bu mu?' Demeliydim.” Alice kendini düzeltti.
“Hayır, yapmamalısın: bu başka bir şey! Şarkının adı 'Ways And Means': ama sadece buna denir, bilirsiniz! ”
“Peki, şarkı nedir o zaman?” dedi Alice, o zamana kadar tamamen şaşkına dönmüştü.
"Ben de buna gelecektim," dedi Şövalye. “Şarkı gerçekten 'Bir Kapıda A-Oturmak': ve şarkı benim icadım.”
Bunu okumak isteyebilirsiniz: İşaretçilerden İşaretçilere
Umarım bu bazı temel şüpheleri açıklığa kavuşturur.
İşaretçiye başvuru gerektiğinde. Örneğin, çağrılan bir işlevin kapsamı içinde çağrılan bir işlevin içinde bildirilen bir işaretçi değişkeninin değerini (adrese işaret eder) değiştirmek istediğinizde.
Bağımsız değişken olarak tek bir işaretçiyi iletirseniz, çağıranın kapsamındaki orijinal işaretçiyi değil, işaretçinin yerel kopyalarını değiştirirsiniz. İşaretçiye bir işaretçiyle, ikincisini değiştirirsiniz.
İşaretçiye işaretçi de tanıtıcı olarak adlandırılır . Bunun için bir kullanım genellikle bir nesnenin bellekte taşınabilmesi veya kaldırılabilmesidir. Kişi genellikle nesnenin kullanımını kilitlemek ve kilidini açmaktan sorumludur, böylece nesneye erişilirken taşınmaz.
Genellikle bellek sınırlı ortamlarda, yani Palm OS'de kullanılır.
Bu kavramı daha iyi anlamak için aşağıdaki şekli ve programı düşünün .
Şekil göre, ptr1 a, tek işaretçi değişken adresini sahip num .
ptr1 = #
Benzer şekilde ptr2 a, işaretçi (çift işaretçi) işaretçi işaretçi adresini sahip ptr1 .
ptr2 = &ptr1;
Başka bir işaretçiyi gösteren işaretçi çift işaretçi olarak bilinir. Bu örnekte ptr2 bir çift işaretleyicidir.
Yukarıdaki diyagramdan değerler:
Address of variable num has : 1000
Address of Pointer ptr1 is: 2000
Address of Pointer ptr2 is: 3000
Misal:
#include <stdio.h>
int main ()
{
int num = 10;
int *ptr1;
int **ptr2;
// Take the address of var
ptr1 = #
// Take the address of ptr1 using address of operator &
ptr2 = &ptr1;
// Print the value
printf("Value of num = %d\n", num );
printf("Value available at *ptr1 = %d\n", *ptr1 );
printf("Value available at **ptr2 = %d\n", **ptr2);
}
Çıktı:
Value of num = 10
Value available at *ptr1 = 10
Value available at **ptr2 = 10
işaretçinin adres değerinin bir göstergesidir. (bu korkunç bir şey biliyorum)
temel olarak, bir işaretçiyi başka bir işaretçinin adresinin değerine geçirmenize izin verir, böylece aşağıdaki gibi bir alt işlevden başka bir işaretçinin işaret ettiği yeri değiştirebilirsiniz:
void changeptr(int** pp)
{
*pp=&someval;
}
İşaretçiye işaretçi, işaretçiye bir işaretçidir.
SomeType ** 'ın anlamlı bir örneği iki boyutlu bir dizidir: diğer dizilere işaretçilerle dolu bir diziniz vardır, bu nedenle
DPointer [5] [6]
5. konumunda diğer dizilere işaretçiler içeren diziye erişirsiniz, işaretçiyi alırsınız (adını fpointer olarak gösterir) ve sonra diziye başvurulan dizinin 6. öğesine erişirsiniz (yani, fpointer [6]).
Nasıl çalışır: Başka bir işaretçiyi saklayabilen bir değişkendir.
Bunları ne zaman kullanırsınız: Birçoğu bunlardan birini kullanır, işleviniz bir dizi oluşturmak ve onu arayana geri döndürmek istiyorsa.
//returns the array of roll nos {11, 12} through paramater
// return value is total number of students
int fun( int **i )
{
int *j;
*i = (int*)malloc ( 2*sizeof(int) );
**i = 11; // e.g., newly allocated memory 0x2000 store 11
j = *i;
j++;
*j = 12; ; // e.g., newly allocated memory 0x2004 store 12
return 2;
}
int main()
{
int *i;
int n = fun( &i ); // hey I don't know how many students are in your class please send all of their roll numbers.
for ( int j=0; j<n; j++ )
printf( "roll no = %d \n", i[j] );
return 0;
}
İşaretçilerin nasıl çalıştığını açıklayan 5 dakikalık bir video hazırladım:
Yararlı açıklamalar çok var, ama ben sadece kısa bir açıklama bulunamadı, bu yüzden ..
Temelde işaretçi değişkenin adresidir. Kısa özet kodu:
int a, *p_a;//declaration of normal variable and int pointer variable
a = 56; //simply assign value
p_a = &a; //save address of "a" to pointer variable
*p_a = 15; //override the value of the variable
//print 0xfoo and 15
//- first is address, 2nd is value stored at this address (that is called dereference)
printf("pointer p_a is having value %d and targeting at variable value %d", p_a, *p_a);
Ayrıca yararlı bilgiler referans ve başvurunun ne anlama geldiği konusunda bulunabilir.
Ve emin değilim, ne zaman yararlı işaretçiler olabilir, ancak ortak olarak bazı manuel / dinamik bellek ayırma - malloc, calloc, vb.
Bu yüzden umarım sorunlu olanı açıklığa kavuşturmaya yardımcı olur :)