Bu bulmacanın üç parçası var.
İlk parça, C ve C ++ 'daki beyaz boşluğun normalde, aksi takdirde ayırt edilemeyen bitişik simgeleri ayırmanın ötesinde önemli olmadığıdır.
Ön işleme aşamasında, kaynak metin bir dizi jetona bölünür - tanımlayıcılar, noktalayıcılar, sayısal değişmezler, dize değişmezleri vb. Bu simge dizisi daha sonra sözdizimi ve anlam açısından analiz edilir. Tokenleştirici "açgözlüdür" ve mümkün olan en uzun geçerli jetonu oluşturacaktır. Gibi bir şey yazarsan
inttest;
simge belirleyici yalnızca iki simge görür - tanımlayıcı ve inttest
ardından noktalama işareti ;
. int
Bu aşamada ayrı bir anahtar kelime olarak tanınmaz (bu, işlemin sonraki aşamalarında gerçekleşir). Dolayısıyla, satırın adlandırılmış bir tamsayının bildirimi olarak okunması test
için, tanımlayıcı jetonlarını ayırmak için boşlukları kullanmamız gerekir:
int test;
*
Karakter herhangi tanımlayıcı bir parçası değildir; kendi başına ayrı bir belirteç (noktalama işareti). Yani yazarsan
int*test;
derleyici, 4 ayrı belirteçleri görür - int
, *
, test
ve ;
. Bu nedenle, işaretçi bildirimlerinde boşluk önemli değildir ve tüm
int *test;
int* test;
int*test;
int * test;
aynı şekilde yorumlanır.
Bulmacanın ikinci parçası, bildirimlerin aslında C ve C ++ 1'de nasıl çalıştığıdır . Bildirimler iki ana parçaya bölünür - bir dizi bildirim belirteçleri (depolama sınıfı belirleyicileri, tür belirleyicileri, tür niteleyicileri, vb.) Ve ardından virgülle ayrılmış (muhtemelen başlatılmış) bildiriciler listesi . Beyannamede
unsigned long int a[10]={0}, *p=NULL, f(void);
deklarasyon belirticilere vardır unsigned long int
ve declarators vardır a[10]={0}
, *p=NULL
ve f(void)
. Bildirici, bildirilen şeyin adını ( a
, p
ve f
) bunun yanı sıra o şeyin dizi özelliği, işaretçi ve işlevi hakkındaki bilgileri tanıtır . Bir bildirici ayrıca ilişkili bir başlatıcıya sahip olabilir.
Türü a
"10 öğeli dizi unsigned long int
" dir. Bu tür, bildirim belirteçleri ve bildiricinin birleşimiyle tam olarak belirtilir ve ilk değer, başlatıcıyla belirtilir ={0}
. Benzer şekilde, türü p
"gösterici unsigned long int
" dir ve yine bu tür, bildirim belirteçleri ve bildiricinin birleşimiyle belirtilir ve olarak başlatılır NULL
. Ve türü de aynı mantıkla " f
dönen fonksiyondur unsigned long int
".
Bu anahtardır - "işlev döndüren" tür belirticisinin olmaması gibi "dizi-türü" tür belirticisi olmaması gibi "işaretçi" türü belirticisi de yoktur. Bir diziyi şu şekilde tanımlayamayız
int[10] a;
çünkü []
operatörün işlenenleri a
değil int
. Benzer şekilde beyannamede
int* p;
göreni *
ise p
, değil int
. Ancak, yönlendirme operatörü tekli olduğundan ve boşluk önemli olmadığından, bu şekilde yazarsak derleyici şikayet etmeyecektir. Ancak, her zaman olarak yorumlanır int (*p);
.
Bu nedenle yazarsan
int* p, q;
işleneni *
olduğu p
için şu şekilde yorumlanacaktır:
int (*p), q;
Böylece hepsi
int *test1, test2;
int* test1, test2;
int * test1, test2;
aynı şeyi yapın - her üç durumda test1
da, işlenendir *
ve bu nedenle türü "işaretçi int
", test2
type vardır int
.
Bildirimciler keyfi olarak karmaşık hale gelebilir. Dizileriniz olabilir:
T *a[N];
dizilere işaretçileriniz olabilir:
T (*a)[N];
işaretçi döndüren işlevlere sahip olabilirsiniz:
T *f(void);
işlevler için işaretçileriniz olabilir:
T (*f)(void)
işlevlere işaretçiler dizisine sahip olabilirsiniz:
T (*a[N])(void)
dizilere işaretçi döndüren işlevlere sahip olabilirsiniz:
T (*f(void))[N];
işaretçi dizilerine işaretçi döndüren işlevlere, işaretçileri şu işlevlere döndüren işlevlere sahip olabilirsiniz T
:
T *(*(*f(void))[N])(void)
ve sonra sahipsin signal
:
void (int)))(int);
hangi okur
signal -- signal
signal( ) -- is a function taking
signal( ) -- unnamed parameter
signal(int ) -- is an int
signal(int, ) -- unnamed parameter
signal(int, (*) ) -- is a pointer to
signal(int, (*)( )) -- a function taking
signal(int, (*)( )) -- unnamed parameter
signal(int, (*)(int)) -- is an int
signal(int, void (*)(int)) -- returning void
(*signal(int, void (*)(int))) -- returning a pointer to
(*signal(int, void (*)(int)))( ) -- a function taking
(*signal(int, void (*)(int)))( ) -- unnamed parameter
(*signal(int, void (*)(int)))(int) -- is an int
void (*signal(int, void (*)(int)))(int)
ve bu mümkün olanın yüzeyini zar zor çiziyor. Ancak, dizinin, işaretleyicinin ve işlevin her zaman tür belirticisinin değil, tanımlayıcının parçası olduğuna dikkat edin.
Dikkat edilmesi gereken bir şey - const
hem işaretçi türünü hem de işaret edilen yazıyı değiştirebilir:
const int *p;
int const *p;
Yukarıdakilerin her ikisi de p
bir const int
nesneye işaretçi olarak bildirilir . p
Farklı bir nesneyi işaret edecek şekilde ayarlamak için yeni bir değer yazabilirsiniz :
const int x = 1;
const int y = 2;
const int *p = &x;
p = &y;
ancak işaret edilen nesneye yazamazsınız:
*p = 3;
Ancak,
int * const p;
const olmayan p
bir const
işaretçi olarak bildirir int
; şunun p
işaret ettiği şeyi yazabilirsin
int x = 1;
int y = 2;
int * const p = &x;
*p = 3;
ancak p
farklı bir nesneyi göstermeyi ayarlayamazsınız :
p = &y
Bu da bizi bulmacanın üçüncü parçasına getiriyor - neden bildirimler bu şekilde yapılandırıldı?
Amaç, bir bildirimin yapısının koddaki bir ifadenin yapısını yakından yansıtmasıdır ("bildirim taklitleri kullanımı"). Örneğin, int
adlandırılacak bir işaretçiler dizisine sahip ap
olduğumuzu int
ve i
'inci öğenin işaret ettiği değere erişmek istediğimizi varsayalım . Bu değere şu şekilde erişiriz:
printf( "%d", *ap[i] );
Sentezleme *ap[i]
türü vardır int
; bu nedenle beyanı ap
şöyle yazılır
int *ap[N];
Bildirici *ap[N]
, ifade ile aynı yapıya sahiptir *ap[i]
. Operatörler *
ve []
bir ifadede yaptıkları gibi bir bildirimde aynı şekilde davranırlar - []
tek terimden daha yüksek önceliğe sahiptir *
, bu nedenle işleneni *
is ap[N]
(şu şekilde ayrıştırılır:*(ap[N])
).
Başka bir örnek olarak, int
adlandırılmış bir diziye bir göstericimiz olduğunu pa
ve i
'inci öğenin değerine erişmek istediğimizi varsayalım . Bunu şu şekilde yazardık
printf( "%d", (*pa)[i] )
İfadenin türü (*pa)[i]
olduğu int
için beyan şu şekilde yazılır:
int (*pa)[N];
Yine, aynı öncelik ve birliktelik kuralları geçerlidir. Bu durumda, i
' ın ' inci elemanına pa
başvurmak istemiyoruz i
, neyi pa
işaret ettiğinin 'inci elemanına erişmek istiyoruz , bu yüzden *
operatörü açıkça gruplandırmalıyızpa
.
*
, []
Ve ()
operatörler bir parçasıdır ifade kodunda, bu nedenle bir parçasıdır Bildiricisi bildiriminde. Bildirici, nesneyi bir ifadede nasıl kullanacağınızı söyler. Eğer gibi bir bildiriminiz varsa int *p;
, bu *p
size kodunuzdaki ifadenin bir int
değer vereceğini söyler . Uzantı olarak, ifadenin p
"işaretçi int
" türünde bir değer verdiğini veya int *
.
Öyleyse, ya benzer şeyleri ya da bunun gibi şeyleri sizeof
kullandığımız oyuncular ve ifadeler gibi şeyler ne olacak ? Gibi bir şeyi nasıl okurum(int *)
sizeof (int [10])
void foo( int *, int (*)[10] );
Tanımlayıcı yok, *
ve []
operatörleri türü doğrudan değiştirmiyor mu?
Şey, hayır - sadece boş bir tanımlayıcıyla ( soyut bir bildirici olarak bilinir ) hala bir tanımlayıcı var . Biz sembol  boş bir tanımlayıcı temsil ediyorsa, o zaman bu şeyleri okuyabilir olarak (int *λ)
, sizeof (int λ[10])
ve
void foo( int *λ, int (*λ)[10] );
ve tıpkı diğer beyanlar gibi davranırlar. int *[10]
10 işaretli bir diziyi temsil ederken int (*)[10]
, bir diziye bir göstericiyi temsil eder.
Ve şimdi bu cevabın kararlı kısmı. Basit işaretçileri şu şekilde bildiren C ++ kuralından hoşlanmıyorum
T* p;
ve aşağıdaki nedenlerden dolayı kötü bir uygulama olduğunu düşünün :
- Sözdizimi ile tutarlı değil;
- Bu soru ile bu soruya tüm çiftleri, anlamı hakkında sorular kanıtlandığı üzere (karışıklığı tanıtır
T* p, q;
, tüm çiftleri bu sorulara, vs.);
- Kendi içinde tutarlı değildir -
T* a[N]
kullanımla asimetrik olduğu gibi bir dizi işaretçi bildirmek (yazma alışkanlığınız yoksa * a[i]
);
- Diziye işaretçi veya işleve işaretçi türlerine uygulanamaz (yalnızca
T* p
kuralı temiz bir şekilde uygulayabilmeniz için bir typedef oluşturmadığınız sürece , ki ... hayır );
- Bunu yapmanın nedeni - "nesnenin göstericiliğini vurgular" - sahtedir. Dizi veya işlev türlerine uygulanamaz ve bu niteliklerin vurgulamak kadar önemli olduğunu düşünüyorum.
Sonunda, sadece iki dilin tip sistemlerinin nasıl çalıştığına dair kafa karışıklığı olduğunu gösteriyor.
Öğeleri ayrı ayrı beyan etmek için iyi nedenler vardır; kötü bir uygulama etrafında çalışmak ( T* p, q;
) bunlardan biri değildir. Açıklayıcılarınızı doğru yazarsanız ( T *p, q;
) kafa karışıklığına neden olma olasılığınız azalır.
Tüm basit for
döngülerinizi kasıtlı olarak şu şekilde yazmaya benzediğini düşünüyorum:
i = 0;
for( ; i < N; )
{
...
i++
}
Sözdizimsel olarak geçerli, ancak kafa karıştırıcı ve niyet muhtemelen yanlış yorumlanacak. Bununla birlikte, T* p;
kural C ++ topluluğunda yerleşiktir ve bunu kendi C ++ kodumda kullanıyorum çünkü kod tabanındaki tutarlılık iyi bir şeydir, ancak bunu her yaptığımda kaşındırıyor.
- C terminolojisini kullanacağım - C ++ terminolojisi biraz farklı, ancak kavramlar büyük ölçüde aynı.