C işaretçiler: ve işareti ve yıldız ne zaman kullanılır?


298

Sadece işaretçilerle başlıyorum ve biraz kafam karıştı. Ben &bir değişkenin adresi anlamına gelir ve bu *işaretçi tarafından işaret nesnenin değerini almak için bir işaretçi değişken önünde kullanılabilir. Ancak, dizilerle, dizelerle çalışırken veya bir değişkenin işaretçi kopyasına sahip işlevleri çağırırken işler farklı çalışır. Tüm bunların içinde bir mantık paterni görmek zor.

Ne zaman kullanmalıyım &ve *?


5
Lütfen bazı şeylerin bazen farklı çalıştığını nasıl gördüğünüzü gösterin. Aksi takdirde, sizi şaşırtan şeyin ne olduğunu tahmin etmeliyiz.
Drew Dormann

1
Neil Butterworth ile aynı fikirde. Muhtemelen bir kitaptan ilk elden çok daha fazla bilgi alacak ve K&R açıklaması oldukça açık.
Tom

146
SO hakkında bu tür sorular sormanın iyi bir fikir olmadığını söyleyen herkese katılmıyorum. SO, Google'da arama yaparken 1 numaralı kaynak haline geldi. Bu cevaplara yeterince kredi vermiyorsunuz. Dan Olson'ın tepkisini okuyun. Bu cevap gerçekten anlayışlı ve yeni başlayanlar için inanılmaz faydalı. RTFMyararsızdır ve açıkçası çok kaba. Cevaplamak için zamanınız yoksa, bu soruları cevaplamak için zaman ayıranlara saygılı olun. Keşke bu "anon" olabilir ama belli ki o anlamlı bir şekilde katkıda bulunmak için zaman yok.
SSH

18
SSH Ne dedi Bu kesinlikle doğrudur. Bazı insanlar "Sadece Google" diye bağırıyor, ancak bugünlerde bunun tersi de geçerli: "Sadece StackOverflow'a bakın." Bu soru birçok insan için yararlıdır. (Bu yüzden upvotes ve hiçbir downvotes.)
MC İmparatoru

Yanıtlar:


611

İşaretçileriniz ve değerleriniz var:

int* p; // variable p is pointer to integer type
int i; // integer value

Bir işaretçiyi aşağıdakilerle bir değere dönüştürürsünüz *:

int i2 = *p; // integer i2 is assigned with integer value that pointer p is pointing to

Bir değeri şu şekilde bir işaretçiye dönüştürürsünüz &:

int* p2 = &i; // pointer p2 will point to the address of integer i

Düzenleme: Diziler durumunda, bunlar çok işaretçiler gibi davranılır. Bunları işaretçi olarak düşünüyorsanız, *yukarıda açıklandığı gibi içlerindeki değerlere ulaşmak için kullanacaksınız , ancak []operatörü kullanmanın başka, daha yaygın bir yolu da var :

int a[2];  // array of integers
int i = *a; // the value of the first element of a
int i2 = a[0]; // another way to get the first element

İkinci öğeyi almak için:

int a[2]; // array
int i = *(a + 1); // the value of the second element
int i2 = a[1]; // the value of the second element

Yani []indeksleme operatörü özel bir şeklidir *operatör ve bu gibi çalışır:

a[i] == *(a + i);  // these two statements are the same thing

2
Nasıl oluyor bu işe yaramıyor? int aX[] = {3, 4}; int *bX = &aX;
Pieter

5
Diziler özeldir ve saydam olarak işaretleyicilere dönüştürülebilir. Bu, bir işaretçiden bir değere ulaşmanın başka bir yolunu vurgular, yukarıdaki açıklamaya ekleyeceğim.
Dan Olson

4
Bunu doğru anlarsam ... örnek int *bX = &aX;işe yaramaz çünkü aXzaten aX[0](yani &aX[0]) adresini döndürür , bu yüzden &aXmantıklı olmayan bir adresin adresini alırsınız. Bu doğru mu?
Pieter

6
Doğru, ancak adresin adresini gerçekten isteyebileceğiniz durumlar olsa da. Bu durumda, int ** bX = & aX olarak ilan edersiniz, ancak bu daha gelişmiş bir konudur.
Dan Olson

3
@ Dan, verilen int aX[] = {3,4};, int **bX = &aX;bir hatadır. &aX"işaretçiden işaretçiye int" değil , " diziden işaretçiye [2] " türündedir int. Özellikle, bir dizinin adı, ilk öğesinin tekli için bir işaretçi olarak değerlendirilmez &. Şunları yapabilirsiniz:int (*bX)[2] = &aX;
Alok Singhal

28

Diziler ve işlevlerle uğraşırken bir örüntü vardır; ilk başta görmek biraz zor.

Dizilerle uğraşırken, aşağıdakileri hatırlamak yararlıdır: çoğu bağlamda bir dizi ifadesi göründüğünde, ifadenin türü dolaylı olarak "N öğesinin T" dizisinden "pointer to T" ye dönüştürülür ve değeri ayarlanır dizideki ilk öğeye işaret etmek için. Bu kuralın istisnaları, dizi ifadesinin &veya sizeofişleçlerinin işleneni olarak göründüğü veya bir bildirimde başlatıcı olarak kullanılan bir dizgi değişmezi olduğu zamandır.

Bu nedenle, dizi ifadesine sahip bir işlevi bağımsız değişken olarak çağırdığınızda, işlev bir dizi değil bir işaretçi alır:

int arr[10];
...
foo(arr);
...

void foo(int *arr) { ... }

Eğer nedeni budur yok kullanmak &"% s" tekabül argümanlar için operatöre scanf():

char str[STRING_LENGTH];
...
scanf("%s", str);

Örtük dönüşüm nedeniyle , dizinin başına işaret scanf()eden bir char *değer alır str. Bu, dizi ifadesi ile bağımsız değişken olarak çağrılan herhangi bir işlev için geçerlidir (hemen hemen tüm str*işlevler *scanfve *printfişlevler vb.).

Pratikte, büyük olasılıkla &işleci kullanarak dizi ifadesi içeren bir işlevi aşağıdaki gibi çağırmazsınız :

int arr[N];
...
foo(&arr);

void foo(int (*p)[N]) {...}

Böyle bir kod çok yaygın değildir; işlev bildirimindeki dizinin boyutunu bilmeniz gerekir ve işlev yalnızca belirli boyutlardaki dizilere işaretçilerle çalışır (10 öğeli T dizisine işaretçi, 11 öğeli diziye işaretçiden farklı bir türdür T).

Bir dizi ifadesi, & operatöre , sonuçta ortaya çıkan ifadenin türü "işaretçi ile T öğesinin T öğesi dizisi" dir veya T (*)[N]bir işaretçi dizisinden ( T *[N]) ve bir işaretçi taban türüne ( T *).

İşlevler ve işaretçilerle uğraşırken hatırlanması gereken kural şudur: bir bağımsız değişkenin değerini değiştirmek ve çağrının koduna yansımasını istiyorsanız, değiştirmek istediğiniz şeye bir işaretçi iletmeniz gerekir. Yine, diziler eserlere biraz İngiliz anahtarı atıyor, ancak önce normal vakalarla ilgileneceğiz.

C'nin tüm işlev bağımsız değişkenlerini değere göre ilettiğini unutmayın; formal parametre, gerçek parametredeki değerin bir kopyasını alır ve formal parametrede yapılan değişiklikler gerçek parametreye yansıtılmaz. Ortak örnek bir takas işlevidir:

void swap(int x, int y) { int tmp = x; x = y; y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(a, b);
printf("after swap: a = %d, b = %d\n", a, b);

Aşağıdaki çıktıyı alırsınız:

takas etmeden önce: a = 1, b = 2
takastan sonra: a = 1, b = 2

Biçimsel parametreler xve yfarklı nesnelerdir ave bdeğişiklikler bu nedenle, xve yyansıtılmaz ave b. Biz değerlerini değiştirmek istediğimiz için ave bbiz geçmelidir işaretçileri takas işlevine onlara:

void swap(int *x, int *y) {int tmp = *x; *x = *y; *y = tmp; }
...
int a = 1, b = 2;
printf("before swap: a = %d, b = %d\n", a, b);
swap(&a, &b);
printf("after swap: a = %d, b = %d\n", a, b);

Şimdi çıktınız

takas etmeden önce: a = 1, b = 2
takastan sonra: a = 2, b = 1

Takas işlevinde, biz değerlerini değişmez, unutmayın xve yama ne değerlerini xve y nokta için . Yazmak *x, yazmaktan farklıdır x; değeri xkendi içinde xgüncellemiyoruz, bu konumdan bir konum alıyor ve bu konumdaki değeri güncelliyoruz.

Bir işaretçi değerini değiştirmek istiyorsak, bu eşit derecede doğrudur; yazarsak

int myFopen(FILE *stream) {stream = fopen("myfile.dat", "r"); }
...
FILE *in;
myFopen(in);

giriş parametresinin değerini değiştiriyoruz, streamneyi stream işaret ettiğini değil , dolayısıyla değiştirmenin streamdeğeri üzerinde hiçbir etkisi yoktur in; bunun çalışması için işaretçiye bir işaretçi geçirmeliyiz:

int myFopen(FILE **stream) {*stream = fopen("myFile.dat", "r"); }
...
FILE *in;
myFopen(&in);

Yine, diziler eserlere biraz İngiliz anahtarı atar. Bir işleve dizi ifadesi ilettiğinizde, işlevin aldığı şey bir işaretçidir. Dizi aboneliğinin nasıl tanımlandığından dolayı, bir işaretçi üzerinde bir alt simge operatörünü bir dizide kullanabileceğiniz gibi kullanabilirsiniz:

int arr[N];
init(arr, N);
...
void init(int *arr, int N) {size_t i; for (i = 0; i < N; i++) arr[i] = i*i;}

Dizi nesnelerinin atanamayabileceğini unutmayın; yani, böyle bir şey yapamazsın

int a[10], b[10];
...
a = b;

dizilere işaretçilerle uğraşırken dikkatli olmak istersiniz; gibi bir şey

void (int (*foo)[N])
{
  ...
  *foo = ...;
}

çalışmaz.


16

Basitçe söyleyin

  • &adresi anlamına gelirse, parametre değişkenini C'deki gibi değiştirme işlevlerinin yer tutucularında, parametre değişkenlerinin değere göre, referans ile geçmek için ve işareti kullanılarak geçtiğini göreceksiniz.
  • *anlamına gelir dereference bu işaretçi değişkeninin değerini elde etmek için, yani bir göstericinin.
int foo(int *x){
   *x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(&y);  // Now y is incremented and in scope here
   printf("value of y = %d\n", y); // output is 6
   /* ... */
}

Yukarıdaki örnek, foopass-by-referans kullanarak bir fonksiyonun nasıl çağrılacağını gösterir , bununla karşılaştır

int foo(int x){
   x++;
}

int main(int argc, char **argv){
   int y = 5;
   foo(y);  // Now y is still 5
   printf("value of y = %d\n", y); // output is 5
   /* ... */
}

İşte bir referansın kullanılmadığını gösteren bir örnek

int main(int argc, char **argv){
   int y = 5;
   int *p = NULL;
   p = &y;
   printf("value of *p = %d\n", *p); // output is 5
}

Yukarıdaki adres-adresini y ve işaretçi değişkenine nasıl atadığımızı göstermektedir p. Sonra KQUEUE p takılarak *değerini elde etmek için bunun önüne p, yani *p.


10

Evet, *C / C ++ 'da birçok farklı amaç için kullanıldığından oldukça karmaşık olabilir .

Eğer *zaten bildirilen değişken / fonksiyon önünde göründüğünü, ya anlamına gelir:

  • a) *söz konusu değişkenin değerine erişim sağlar (söz konusu değişkenin türü bir işaretçi türüyse veya *işleci aşırı yüklüyse).
  • b) *çarpma operatörü anlamına gelir, bu durumda, ekranın solunda başka bir değişken olması gerekir.*

Eğer *bir değişken veya işlev bildiriminde yer alan bu değişken bir işaretçi olduğu anlamına gelir:

int int_value = 1;
int * int_ptr; //can point to another int variable
int   int_array1[10]; //can contain up to 10 int values, basically int_array1 is an pointer as well which points to the first int of the array
//int   int_array2[]; //illegal, without initializer list..
int int_array3[] = {1,2,3,4,5};  // these two
int int_array4[5] = {1,2,3,4,5}; // are identical

void func_takes_int_ptr1(int *int_ptr){} // these two are identical
void func_takes int_ptr2(int int_ptr[]){}// and legal

Eğer &bir değişken veya işlev bildiriminde yer alan, genel olarak bu değişken, bu tür bir değişken için bir referans olduğu anlamına gelir.

Eğer &zaten ilan değişkenin önünde göründüğünü, bu değişkenin adresini verir

Ayrıca, bir diziyi bir işleve geçirirken, dizinin 0 sonlu bir cstring (char dizisi) gibi bir şey olması dışında, her zaman o dizinin dizi boyutunu da geçirmeniz gerektiğini bilmelisiniz.


1
@akmozo s / func_takes int_ptr2 / func_takes_int_ptr2 / (geçersiz alan)
PixnBits

4

İşaretçi değişkeni veya işlev parametresi bildirirken *:

int *x = NULL;
int *y = malloc(sizeof(int)), *z = NULL;
int* f(int *x) {
    ...
}

Not: bildirilen her değişkenin kendine * ihtiyacı vardır.

Bir değerin adresini almak istediğinizde & tuşunu kullanın. Bir işaretçide değeri okumak veya yazmak istediğinizde * tuşunu kullanın.

int a;
int *b;
b = f(&a);
a = *b;

a = *f(&a);

Diziler genellikle sadece işaretçiler gibi ele alınır. Bir işlevde bir dizi parametresi bildirdiğinizde, bunun bir işaretçi olduğunu kolayca bildirebilirsiniz (aynı şey anlamına gelir). Bir diziyi bir işleve ilettiğinizde, aslında ilk öğeye bir işaretçi geçirirsiniz.

İşlev işaretçileri kurallara tam olarak uymayan tek şeydir. & Fonksiyonunu kullanmadan bir fonksiyonun adresini alabilir ve * kullanmadan fonksiyon işaretçisini çağırabilirsiniz.


4

Ben bunun yerine rescue.Here için New South Wales Üniversitesi'nden bir videoya döndü tüm ağız açıklamalar ile bakıyordu basit bir açıklama: Biz adresine sahip bir hücreyi varsa xve değer 7değerinin adresi için sormaya, dolaylı yolu 7olduğunu &7ve dolaylı yolu adresinde değeri istemek için xolan *x.So (cell: x , value: 7) == (cell: &7 , value: *x)içine bakmak .another yol: Johnoturur 7th seatnery *7th seatişaret edecek Johnve &Johnverecek address/ konumunu 7th seat. Bu basit açıklama bana yardımcı oldu ve başkalarına da yardımcı olacağını umuyorum. Mükemmel videonun bağlantısı: buraya tıklayın.

İşte başka bir örnek:

#include <stdio.h>

int main()
{ 
    int x;            /* A normal integer*/
    int *p;           /* A pointer to an integer ("*p" is an integer, so p
                       must be a pointer to an integer) */

    p = &x;           /* Read it, "assign the address of x to p" */
    scanf( "%d", &x );          /* Put a value in x, we could also use p here */
    printf( "%d\n", *p ); /* Note the use of the * to get the value */
    getchar();
}

Eklenti: İşaretçiyi kullanmadan önce her zaman başlat. İşaretçi, programın çökmesine neden olabilecek herhangi bir şeye işaret edecektir, çünkü işletim sistemi sahip olmadığınızı bildiği belleğe erişmenizi engelleyecektir. p = &x;, işaretçiye belirli bir konum atarız.


3

Aslında, pat var, bilmeniz gereken başka bir şey yok :-)

Sadece aşağıdaki bitleri eklerdim:

  • iki işlem spektrumun zıt uçlarıdır. &bir değişkeni alır ve size adresi verir, *bir adresi alır ve değişkeni (veya içeriği) verir.
  • diziler, işlevlere ilettiğinizde işaretçiler için "bozulur".
  • aslında dolaylı olarak birden fazla seviyeye sahip olabilirsiniz ( bir işaretçiye işaretçi char **panlamına gelir .pchar

Farklı çalışmayan şeylere gelince, gerçekten değil:

  • daha önce de belirtildiği gibi, diziler işlevlere aktarıldığında göstericilere (dizideki ilk öğeye) düşer; boyut bilgilerini korumazlar.
  • C'de dizeler yoktur, sadece kural olarak, sıfır ( \0) karakteriyle sonlandırılmış bir karakter dizisini temsil eden karakter dizileri vardır .
  • Bir değişkenin adresini bir işleve ilettiğinizde, değişkenin kendisini değiştirmek için işaretçiyi referanstan kaldırabilirsiniz (normalde değişkenler değere göre geçirilir (diziler hariç)).

3

Sanırım biraz kafan karıştı. İşaretçiler hakkında iyi bir öğretici / kitap okumalısınız.

Bu eğitim yeni başlayanlar için çok iyidir (ne olduğunu &ve ne olduğunu açıkça açıklar *). Ve evet Kenneth Reek'in C'deki Pointers kitabını okumayı unutma .

Arasındaki fark &ve *çok açıktır.

Misal:

#include <stdio.h>

int main(){
  int x, *p;

  p = &x;         /* initialise pointer(take the address of x) */
  *p = 0;         /* set x to zero */
  printf("x is %d\n", x);
  printf("*p is %d\n", *p);

  *p += 1;        /* increment what p points to i.e x */
  printf("x is %d\n", x);

  (*p)++;         /* increment what p points to i.e x */
  printf("x is %d\n", x);

  return 0;
}

1

Tamam, yayınınızın düzenlendiği anlaşılıyor ...

double foo[4];
double *bar_1 = &foo[0];

&Dizi yapısının başlangıç ​​adresini almak için nasıl kullanabileceğinizi görüyor musunuz? Devamındaki

Foo_1(double *bar, int size){ return bar[size-1]; }
Foo_2(double bar[], int size){ return bar[size-1]; }

aynı şeyi yapacaktır.


Soru C ++ değil C olarak etiketlendi.
Prasoon Saurav

1
Ve rahatsız edici cout kaldırdım <<
wheaties
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.