C işaretçi dizi / işaretçi dizileme dizilimi


463

Aşağıdaki bildirimler arasındaki fark nedir:

int* arr1[8];
int (*arr2)[8];
int *(arr3[8]);

Daha karmaşık beyanları anlamak için genel kural nedir?


54
C'deki karmaşık bildirimleri okumakla ilgili harika bir makale: unixwiz.net/techtips/reading-cdecl.html
jesper

@jesper Maalesef constve volatileönemli ve zor hem elemeleri, o makalede eksik.
kullanıcı değil

@ kullanıcı olmayanlar bu soru ile ilgili değildir. Yorumunuz alakalı değil. Lütfen kaçının.
user64742

Yanıtlar:


439
int* arr[8]; // An array of int pointers.
int (*arr)[8]; // A pointer to an array of integers

Üçüncüsü birincisi ile aynıdır.

Genel kural operatör önceliğidir . İşlev işaretçileri resme geldikçe daha da karmaşıklaşabilir.


4
Yani, 32 bit sistemler için: int * arr [8]; / İşaretçi / int (* arr) [8] için ayrılmış 8x4 bayt ; / 4 bayt tahsis edildi, sadece bir işaretçi * /
George

10
Hayır! int * arr [8]: Ayrılan toplam 8x4 bayt , her işaretçi için 4 bayt. int (* arr) [8] doğrudur, 4 bayt.
Mehrdad Afshari

2
Yazdıklarımı tekrar okumalıydım. Her işaretçi için 4 demek istedim. Yardım için teşekkürler!
George

4
Birincisinin sonuncusu ile aynı olmasının nedeni, parantezlerin her zaman bildiricilerin etrafına sarılmasına izin vermesidir. P [N] bir dizi bildiricisidir. P (....) bir işlev bildiricidir ve * P bir işaretçi bildiricidir. Dolayısıyla, aşağıdaki her şey parantez olmadan aynıdır ('"()" işlevlerinden biri hariç: int (((* p))); void ((g (void))); int * (a [1]); boşluk (* (p ())).
Johannes Schaub - litb

2
Açıklamasında aferin. Operatörlerin önceliği ve ilişkilendirilebilirliği hakkında derinlemesine bir referans için Brian Kernighan ve Dennis Ritchie'nin C Programlama Dili (ANSI C ikinci baskısı) sayfa 53'e bakınız. Operatörler ( ) [ ] soldan sağa ve daha yüksek önceliğe sahip ilişkilendirir *çok oku int* arr[8]boyutu 8, bir dizi olarak burada bir int ve her bir öğe noktaları int (*arr)[8]tamsayılar tutan boyutu 8 oluşan bir dizi için bir işaretçi olarak
Mushy

267

K&R'nin önerdiği şekilde cdecl programını kullanın .

$ cdecl
Type `help' or `?' for help
cdecl> explain int* arr1[8];
declare arr1 as array 8 of pointer to int
cdecl> explain int (*arr2)[8]
declare arr2 as pointer to array 8 of int
cdecl> explain int *(arr3[8])
declare arr3 as array 8 of pointer to int
cdecl>

Başka şekilde de çalışır.

cdecl> declare x as pointer to function(void) returning pointer to float
float *(*x)(void )

@ankii Çoğu Linux dağıtımının bir paketi olmalıdır. Ayrıca kendi ikili dosyalarınızı da oluşturabilirsiniz.
sigjuice

Bahsetmediğim için üzgünüm, macOS burada. Varsa göreceksiniz, aksi takdirde web sitesi de iyidir. ^^ Bunu bana bildirdiğiniz için teşekkürler .. NLN'yi işaretlemekten çekinmeyin.
ankii

2
@ankii Homebrew'dan (ve belki MacPorts'tan) kurabilirsiniz. Bunlar zevkinize uygun değilse, cdecl.org'un sağ üst köşesindeki Github bağlantısından kendiniz oluşturmak önemsizdir (sadece macOS Mojave üzerine inşa ettim). Sonra cdecl ikili dosyasını PATH'nize kopyalayın. Ben $ PATH / bin öneririz, çünkü bu kadar basit bir şey kök dahil etmeye gerek yoktur.
sigjuice

Benioku kurulumuyla ilgili küçük paragrafı okumamıştım. bağımlılıkları işlemek için sadece bazı komutlar ve bayraklar. :)
ankii

1
Bunu ilk okuduğumda, "asla bu seviyeye inmeyeceğim" diye düşündüm. Ertesi gün indirdim.
Ciro Santilli 法轮功 11: 病 六四 事件 法轮功

126

Resmi bir isme sahip olup olmadığını bilmiyorum, ama ona Sağ-Sol Thingy (TM) diyorum.

Değişkenle başlayın, sonra sağa, sola ve sağa gidin ... vb.

int* arr1[8];

arr1 tamsayılara 8 işaretli bir dizidir.

int (*arr2)[8];

arr2 8 tamsayıdan oluşan bir diziye bir işaretçi (parantez bloğu sağ-sol).

int *(arr3[8]);

arr3 tamsayılara 8 işaretli bir dizidir.

Bu, karmaşık bildirimlerde size yardımcı olacaktır.


19
Burada bulunan "Spiral Kural" adıyla anıldığını duydum .
fouric

6
@InkBlend: Spiral kuralı sağ-sol kuraldan farklı . Birincisi , ikincisinin başarılı olduğu gibi durumlarda başarısızint *a[][10] olur.
legends2k

1
@dogeen Bu terimin Bjarne Stroustrup ile ilgisi olduğunu düşündüm :)
Anirudh Ramanathan

1
InkBlend ve legends2k'nin dediği gibi, bu daha karmaşık ve her durumda çalışmayan Spiral Kural'dır, bu yüzden kullanmak için bir neden yoktur.
kotlomoy

( ) [ ]Sol ve sağ sol sol tarafa sol unutmayın* &
Mushy

28
int *a[4]; // Array of 4 pointers to int

int (*a)[4]; //a is a pointer to an integer array of size 4

int (*a[8])[5]; //a is an array of pointers to integer array of size 5 

Üçüncüsü olmamalı: a, 8 boyutlu tamsayı dizisine bir işaretçi dizisi mi? Yani tamsayı dizilerinin her biri 8 büyüklüğünde olacak değil mi?
Rushil Paul

2
@Rushil: hayır, son alt simge ( [5]) iç boyutu temsil eder. Bu (*a[8]), ilk boyut olduğu ve dolayısıyla dizinin dış temsili olduğu anlamına gelir . Her bir öğenin a işaret ettiği nokta, 5 büyüklüğünde farklı bir tamsayı dizisidir.
zeboidlund

Üçüncüsü için teşekkürler. Nasıl dizi için işaretçi dizisi yazmak için arıyorum.
Deqing

15

Son ikisinin cevabı C'deki altın kuraldan da çıkarılabilir:

Beyanname kullanımı takip eder.

int (*arr2)[8];

Kayıttan çıkarılırsanız ne olur arr2? 8 tamsayı bir dizi alırsınız.

int *(arr3[8]);

Bir elemanı alırsanız ne olur arr3? Bir tamsayıya bir işaretçi alırsınız.

Bu aynı zamanda işlevlere işaretçilerle uğraşırken de yardımcı olur. Sigjuice örneğini ele almak için:

float *(*x)(void )

Vazgeçtiğinizde ne olur x? Bağımsız değişken olmadan arayabileceğiniz bir işlev elde edersiniz. Ne zaman diyorsun? İşaretçiyi a işaretine döndürür float.

Operatör önceliği her zaman zor. Ancak, bildirim kullanımdan sonra parantez kullanmak aslında kafa karıştırıcı olabilir. En azından, bana göre, sezgisel arr2olarak 8 işaretçi bir dizi gibi görünüyor, ama aslında tam tersi. Sadece alışmak biraz zaman alır. Bana sorarsanız, bu bildirimlere her zaman bir yorum eklemek için yeterli neden :)

düzenle: örnek

Bu arada, sadece aşağıdaki duruma rastladım: statik bir matrisi olan ve satır işaretçisinin sınırların dışında olup olmadığını görmek için işaretçi aritmetiği kullanan bir işlev. Misal:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define NUM_ELEM(ar) (sizeof(ar) / sizeof((ar)[0]))

int *
put_off(const int newrow[2])
{
    static int mymatrix[3][2];
    static int (*rowp)[2] = mymatrix;
    int (* const border)[] = mymatrix + NUM_ELEM(mymatrix);

    memcpy(rowp, newrow, sizeof(*rowp));
    rowp += 1;
    if (rowp == border) {
        rowp = mymatrix;
    }

    return *rowp;
}

int
main(int argc, char *argv[])
{
    int i = 0;
    int row[2] = {0, 1};
    int *rout;

    for (i = 0; i &lt; 6; i++) {
        row[0] = i;
        row[1] += i;
        rout = put_off(row);
        printf("%d (%p): [%d, %d]\n", i, (void *) rout, rout[0], rout[1]);
    }

    return 0;
}

Çıktı:

0 (0x804a02c): [0, 0]
1 (0x804a034): [0, 0]
2 (0x804a024): [0, 1]
3 (0x804a02c): [1, 2]
4 (0x804a034): [2, 4]
5 (0x804a024): [3, 7]

Sınır değerinin asla değişmediğini unutmayın, böylece derleyici bunu optimize edebilir. Bu, başlangıçta kullanmak isteyebileceğinizden farklıdır: const int (*border)[3]değişken, var olduğu sürece değeri değişmeyecek 3 tamsayıdan oluşan bir diziye işaretçi olarak bildirir. Bununla birlikte, bu işaretçi herhangi bir zamanda bu tür başka bir diziye yönlendirilebilir. Bunun yerine argüman için bu tür davranışlar istiyoruz (çünkü bu işlev bu tamsayıların hiçbirini değiştirmez). Beyanname kullanımı takip eder.

(ps: bu örneği geliştirmek için çekinmeyin!)



3

Genel bir kural sağ birli operatörler (gibi gibi [], ()sol olanlar üzerinde, vs) önceliklidir. Bu nedenle, int *(*ptr)()[];int'e bir işaretçi dizisi döndüren bir işleve işaret eden bir işaretçi olurdu (parantezten çıkarken mümkün olan en kısa sürede doğru işleçleri alın)


Bu doğru, ama aynı zamanda ilegal. Dizi döndüren bir işleve sahip olamazsınız. Bunu denedim ve aldım: error: ‘foo’ declared as function returning an array int foo(int arr_2[5][5])[5];GCC 8 ile$ gcc -std=c11 -pedantic-errors test.c
Cacahuete Frito

1
Derleyicinin bu hatayı vermesinin nedeni, işlevi bir dizi döndürme olarak yorumlaması, öncelik kuralı durumlarının doğru yorumu olarak yorumlanmasıdır. Bir deklarasyon olarak ilegaldir, ancak yasal deklarasyon daha sonra (veya ) int *(*ptr)();gibi bir ifadenin kullanılmasına izin verir . p()[3](*p)()[3]
Luis Colorado

Tamam, anlıyorsam, bir dizinin ilk öğesine (dizinin kendisi değil) bir işaretçi döndüren bir işlev oluşturmaktan bahsediyorsunuz ve daha sonra bu işlevi bir dizi döndürüyormuş gibi mi kullanıyorsunuz? İlginç fikir. Deneyeceğim. int *foo(int arr_2[5][5]) { return &(arr_2[2][0]); }ve şöyle deyin: foo(arr)[4];hangisini içermeli arr[2][4], değil mi?
Cacahuete Frito

doğru ... ama sen de haklıydın ve açıklama ilegal'di. :)
Luis Colorado

2

Bence basit kuralı kullanabiliriz ..

example int * (*ptr)()[];
start from ptr 

" ptrsağa gitmek için bir işaretçi ..its") "şimdi sola git onun" ("dışarı çık sağa git" () "so" hiçbir argümanı almayan bir fonksiyona "sola git" ve bir işaretçi döndürür "git sağ "diziye" tamsayıların sola git "


Bunu biraz geliştiririm: "ptr" sağa git ... "anlamına gelir ), şimdi sola git ... bu *" sağa git "için bir işaretçi ... o ), şimdi sola git ... bir (sağa git, çıkıp ()... sağa git "hayır argümanlar alır bir işleve" öylesine []"ve iadeler dizisi" doğru gitmek ;ucunu, yani sola git ... *"işaretçileri için" git sola ... int"tamsayılar"
Cacahuete Frito


2

İşte nasıl yorumlayacağım:

int *something[n];

Önceliğe ilişkin not: dizi alt simge operatörü ( []), dereference operatörüne ( *) göre daha yüksek önceliğe sahiptir .

Yani, burada daha []önce uygulayacağız *ve ifadeyi şuna eşit hale getireceğiz:

int *(something[i]);

Bir beyan duygusunu ilgili Not: int numaracı num, bir olduğuint , int *ptrya da int (*ptr)anlamına gelir, (en değeri ptr) bir olduğu intkılan ptrbir işaretçi int.

Bu, (bir şeyin i indeksindeki değer) değeri bir tamsayı olarak okunabilir. Yani, (bir şeyin ith indeksindeki değer), bir şeyi tamsayı işaretçileri dizisi yapan bir (tamsayı işaretçisi) 'dir.

İkincisinde,

int (*something)[n];

Bu ifadeden bir anlam çıkarmak için bu gerçeğe aşina olmalısınız:

Dizinin seçilen gösterim ile Not: somethingElse[i]eşdeğerdir*(somethingElse + i)

Yani, yerine somethingElseile (*something), aldığımız *(*something + i)bildiri uyarınca bir tamsayı olan. Yani, (*something)bize bir dizi (bu bir diziye işaretçi) eşdeğer yapar bir dizi verildi .


0

Sanırım ikinci beyan birçok insan için kafa karıştırıcı. İşte bunu anlamanın kolay bir yolu.

Bir tamsayı dizisine sahip olalım, yani int B[8].

Ayrıca B'yi gösteren bir A değişkenine sahip olalım. Şimdi, A'daki değer B'dir, yani (*A) == B. Bu nedenle A, bir tamsayı dizisini gösterir. Sorunuzda, arr A'ya benzer.

Benzer şekilde, içinde int* (*C) [8]C, tamsayıya bir işaretçi dizisinin göstergesidir.


0
int *arr1[5]

Bu bildirimde, arr1tamsayılara 5 işaretçi dizisidir. Sebep: Köşeli parantezlerin önceliği * (kayıt silme operatörü) üzerinde daha yüksektir. Ve bu tipte, satır sayısı sabittir (burada 5), ​​ancak sütun sayısı değişkendir.

int (*arr2)[5]

Bu bildirimde, arr25 öğeden oluşan bir tamsayı dizisinin göstergesidir. Sebep: Burada, () köşeli ayraçlar [] 'den daha yüksek önceliğe sahiptir. Ve bu tipte, satır sayısı değişkendir, ancak sütun sayısı sabittir (burada 5).


-7

Bir tamsayıya işaretçi içinde, işaretçi artırılırsa, bir sonraki tamsayıya gider.

işaretçi dizisinde işaretçi artırılırsa sonraki diziye atlar


" işaretçi dizisinde işaretçi artırılırsa sonraki diziye atlar " bu yanlıştır.
alk
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.