Bagaimana cara menjelaskan C pointer (deklarasi vs operator unary) kepada seorang pemula?

141

Saya baru-baru ini senang menjelaskan pointer ke pemula pemrograman C dan menemukan kesulitan berikut. Mungkin tidak tampak seperti masalah sama sekali jika Anda sudah tahu cara menggunakan pointer, tetapi cobalah untuk melihat contoh berikut dengan pikiran yang jernih:

int foo = 1;
int *bar = &foo;
printf("%p\n", (void *)&foo);
printf("%i\n", *bar);

Bagi pemula absolut hasilnya mungkin mengejutkan. Pada baris 2 dia baru saja menyatakan * bar sebagai & foo, tetapi pada baris 4 ternyata * bar sebenarnya foo bukan & foo!

Kebingungan, Anda mungkin mengatakan, berasal dari ambiguitas simbol *: Pada baris 2 digunakan untuk mendeklarasikan pointer. Pada baris 4 digunakan sebagai operator unary yang mengambil nilai titik penunjuk. Dua hal yang berbeda, bukan?

Namun, "penjelasan" ini sama sekali tidak membantu pemula. Ini memperkenalkan konsep baru dengan menunjukkan perbedaan yang halus. Ini bukan cara yang tepat untuk mengajarkannya.

Jadi, bagaimana Kernighan dan Ritchie menjelaskannya?

Operator unary * adalah operator tipuan atau dereferencing; ketika diterapkan pada sebuah pointer, ia mengakses objek yang ditunjuk oleh pointer itu. [...]

Deklarasi pointer ip, int *ipdimaksudkan sebagai mnemonik; dikatakan bahwa ekspresinya *ipadalah int. Sintaks deklarasi untuk variabel meniru sintaks ekspresi di mana variabel mungkin muncul .

int *ipharus dibaca seperti " *ipakan mengembalikan sebuah int"? Tetapi mengapa kemudian penugasan setelah deklarasi tidak mengikuti pola itu? Bagaimana jika seorang pemula ingin menginisialisasi variabel? int *ip = 1(baca: *ipakan mengembalikan intdan intis 1) tidak akan berfungsi seperti yang diharapkan. Model konseptual sepertinya tidak koheren. Apakah saya melewatkan sesuatu di sini?


Sunting: Ini mencoba merangkum jawaban di sini .

armin
sumber
15
Penjelasan terbaik adalah dengan menggambar sesuatu di atas kertas dan menghubungkannya dengan panah;)
Maroun
16
Ketika saya harus menjelaskan sintaks pointer, saya selalu menekankan fakta bahwa *dalam sebuah deklarasi adalah makna token "menyatakan pointer", dalam ekspresi itu adalah operator dereference, dan bahwa keduanya mewakili hal-hal berbeda yang kebetulan memiliki simbol yang sama (sama seperti operator perkalian - simbol yang sama, makna yang berbeda). Ini membingungkan, tetapi sesuatu yang berbeda dari keadaan sebenarnya akan menjadi lebih buruk.
Matteo Italia
40
mungkin menulisnya int* barmembuatnya lebih jelas bahwa bintang itu sebenarnya bagian dari tipe, bukan bagian dari pengenal. Tentu saja ini membawa Anda ke masalah yang berbeda dengan hal-hal yang tidak intuitif seperti int* a, b.
Niklas B.
9
Saya selalu berpikir bahwa penjelasan K&R konyol dan tidak perlu. Bahasa menggunakan simbol yang sama untuk dua hal yang berbeda dan kita hanya harus menghadapinya. *dapat memiliki dua arti berbeda tergantung pada konteksnya. Sama seperti huruf yang sama dapat diucapkan berbeda tergantung pada kata itu di mana membuatnya sulit untuk belajar berbicara banyak bahasa. Jika setiap konsep / operasi memiliki simbolnya sendiri, kita akan membutuhkan keyboard yang lebih besar, jadi simbol tersebut didaur ulang ketika masuk akal untuk melakukannya.
Seni
8
Saya telah mengalami masalah yang sama berkali-kali ketika mengajar C kepada orang lain dan, dalam pengalaman saya, itu dapat diselesaikan dengan cara yang disarankan kebanyakan orang di sini. Pertama, jelaskan konsep penunjuk tanpa sintaks C. Kemudian, ajarkan sintaksis dan tekankan asterisk sebagai bagian dari tipe ( int* p), sembari memperingatkan siswa Anda agar tidak menggunakan banyak deklarasi di baris yang sama ketika pointer terlibat. Ketika siswa telah sepenuhnya memahami konsep pointer, jelaskan kepada siswa bahwa int *psintaks is adalah setara dan kemudian jelaskan masalahnya dengan beberapa deklarasi.
Theodoros Chatzigiannakis

Jawaban:

43

Agar siswa Anda memahami makna *simbol dalam konteks yang berbeda, mereka harus terlebih dahulu memahami bahwa konteksnya memang berbeda. Begitu mereka memahami bahwa konteksnya berbeda (yaitu perbedaan antara sisi kiri dari tugas dan ekspresi umum) itu tidak terlalu banyak lompatan kognitif untuk memahami apa perbedaannya.

Pertama, jelaskan bahwa deklarasi variabel tidak boleh berisi operator (tunjukkan hal ini dengan menunjukkan bahwa menempatkan a -atau+ simbol dalam deklarasi variabel hanya menyebabkan kesalahan). Kemudian lanjutkan untuk menunjukkan bahwa ekspresi (yaitu di sisi kanan penugasan) dapat berisi operator. Pastikan siswa memahami bahwa ekspresi dan deklarasi variabel adalah dua konteks yang sama sekali berbeda.

Ketika mereka memahami bahwa konteksnya berbeda, Anda bisa menjelaskan bahwa ketika *simbol ada dalam deklarasi variabel di depan pengenal variabel, itu berarti 'mendeklarasikan variabel ini sebagai penunjuk'. Kemudian Anda dapat menjelaskan bahwa ketika digunakan dalam ekspresi (sebagai operator unary) *simbol adalah 'operator dereferensi' dan itu berarti 'nilai pada alamat' daripada makna sebelumnya.

Untuk benar-benar meyakinkan siswa Anda, jelaskan bahwa pencipta C dapat menggunakan simbol apa pun untuk berarti operator dereference (yaitu mereka dapat menggunakan @sebagai gantinya) tetapi untuk alasan apa pun mereka membuat keputusan desain untuk digunakan *.

Secara keseluruhan, tidak ada jalan lain untuk menjelaskan bahwa konteksnya berbeda. Jika siswa tidak memahami konteksnya berbeda, mereka tidak dapat memahami mengapa *simbol tersebut dapat berarti hal yang berbeda.

Pharap
sumber
80

Alasan mengapa tulisan cepat:

int *bar = &foo;

dalam contoh Anda dapat membingungkan adalah bahwa mudah untuk salah membaca sebagai setara dengan:

int *bar;
*bar = &foo;    // error: use of uninitialized pointer bar!

padahal sebenarnya artinya:

int *bar;
bar = &foo;

Ditulis seperti ini, dengan deklarasi variabel dan penugasan dipisahkan, tidak ada potensi kebingungan, dan penggunaan ↔ paralelisme deklarasi yang dijelaskan dalam kutipan K&R Anda berfungsi dengan baik:

  • Baris pertama mendeklarasikan variabel bar, sehingga *barmerupakan int.

  • Baris kedua memberikan alamat footo bar, membuat *bar(an int) alias untuk foo(juga an int).

Saat memperkenalkan sintaks pointer C kepada pemula, mungkin awalnya membantu untuk tetap berpegang pada gaya ini memisahkan deklarasi pointer dari tugas, dan hanya memperkenalkan sintaks steno gabungan (dengan peringatan yang tepat tentang potensi kebingungan) setelah konsep dasar penggunaan pointer di C telah diinternalisasi secara memadai.

Ilmari Karonen
sumber
4
Saya akan tergoda typedef. typedef int *p_int;berarti bahwa variabel tipe p_intmemiliki properti yang *p_intadalah int. Lalu kita punya p_int bar = &foo;. Mendorong siapa pun untuk membuat data yang tidak diinisialisasi dan kemudian menetapkannya sebagai masalah kebiasaan bawaan tampaknya ... seperti ide yang buruk.
Yakk - Adam Nevraumont
6
Ini hanya gaya deklarasi C yang rusak otak; itu tidak spesifik untuk pointer. pertimbangkan int a[2] = {47,11};, itu bukan inisialisasi elemen (tidak ada) lebih mudah a[2].
Marc van Leeuwen
5
@MarcvanLeeuwen Setuju dengan kerusakan otak. Idealnya, *harus menjadi bagian dari tipe, tidak terikat pada variabel, dan kemudian Anda akan dapat menulis int* foo_ptr, bar_ptruntuk mendeklarasikan dua petunjuk. Tetapi sebenarnya menyatakan pointer dan integer.
Barmar
1
Ini bukan hanya tentang deklarasi / tugas "singkatan". Seluruh masalah muncul lagi saat Anda ingin menggunakan pointer sebagai argumen fungsi.
armin
30

Pendek pada deklarasi

Sangat menyenangkan untuk mengetahui perbedaan antara deklarasi dan inisialisasi. Kami mendeklarasikan variabel sebagai tipe dan menginisialisasi mereka dengan nilai. Jika kita melakukan keduanya sekaligus, kita sering menyebutnya definisi.

1. int a; a = 42;

int a;
a = 42;

Kami mendeklarasikan sebuah intbernama sebuah . Lalu kami menginisialisasi dengan memberikan nilai 42.

2. int a = 42;

Kami mendeklarasikan dan memberi intnama a dan memberikannya nilai 42. Ini diinisialisasi dengan 42. Definisi.

3. a = 43;

Ketika kita menggunakan variabel, kita katakan kita mengoperasikannya .a = 43adalah operasi penugasan. Kami menetapkan angka 43 ke variabel a.

Dengan berkata

int *bar;

kami menyatakan bilah sebagai penunjuk ke int. Dengan berkata

int *bar = &foo;

kami mendeklarasikan bilah dan menginisialisasi dengan alamat foo .

Setelah kita menginisialisasi bilah, kita dapat menggunakan operator yang sama, tanda bintang, untuk mengakses dan beroperasi pada nilai foo . Tanpa operator, kami mengakses dan beroperasi pada alamat yang ditunjuk oleh pointer.

Selain itu saya membiarkan gambar berbicara.

Apa

ASCIIMASI yang disederhanakan tentang apa yang sedang terjadi. (Dan di sini versi pemain jika Anda ingin berhenti, dll.)

          ASCIIMASI

Morpfh
sumber
22

Pernyataan ke-2 int *bar = &foo;dapat dilihat secara foto dalam memori sebagai,

   bar           foo
  +-----+      +-----+
  |0x100| ---> |  1  |
  +-----+      +-----+ 
   0x200        0x100

Sekarang baradalah pointer dari jenis intyang mengandung alamat &dari foo. Menggunakan operator unary, *kami menghormati untuk mengambil nilai yang terkandung dalam 'foo' dengan menggunakan pointer bar.

EDIT : Pendekatan saya dengan pemula adalah menjelaskan memory addressvariabel yaitu

Memory Address:Setiap variabel memiliki alamat yang dikaitkan dengan itu yang disediakan oleh OS. Di int a;, &aadalah alamat variabel a.

Lanjutkan menjelaskan tipe dasar variabel dalam Cbentuk,

Types of variables: Variabel dapat menyimpan nilai dari masing-masing jenis tetapi tidak alamat.

int a = 10; float b = 10.8; char ch = 'c'; `a, b, c` are variables. 

Introducing pointers: Seperti yang dikatakan variabel di atas, misalnya

 int a = 10; // a contains value 10
 int b; 
 b = &a;      // ERROR

Dimungkinkan penetapan b = atetapi tidak b = &a, karena variabel bdapat menyimpan nilai tetapi tidak mengatasi, Oleh karena itu kami memerlukan Pointer .

Pointer or Pointer variables :Jika suatu variabel berisi alamat itu dikenal sebagai variabel pointer. Gunakan *dalam deklarasi untuk menginformasikan bahwa itu adalah pointer.

 Pointer can hold address but not value
 Pointer contains the address of an existing variable.
 Pointer points to an existing variable
Sunil Bojanapally
sumber
3
Masalahnya adalah bahwa membaca int *ipsebagai "ip adalah pointer (*) dari tipe int" Anda mendapat masalah saat membaca sesuatu seperti x = (int) *ip.
armin
2
@ ABW Itu sesuatu yang sangat berbeda, maka tanda kurung. Saya tidak berpikir orang akan mengalami kesulitan memahami perbedaan antara deklarasi dan casting.
bzeaman
@abw In x = (int) *ip;, dapatkan nilainya dengan dereferencing pointer ipdan berikan nilainya intdari tipe apa pun ip.
Sunil Bojanapally
1
@ BennoZeeman Anda benar: casting dan deklarasi adalah dua hal yang berbeda. Saya mencoba untuk memberi petunjuk pada peran yang berbeda dari tanda bintang: 1 "ini bukan int, tetapi pointer ke int" 2 "ini akan memberi Anda int, tetapi bukan pointer ke int".
armin
2
@abw: Itulah sebabnya mengajar int* bar = &foo;membuat beban lebih masuk akal. Ya, saya tahu ini menyebabkan masalah ketika Anda mendeklarasikan banyak pointer dalam satu deklarasi. Tidak, saya pikir itu tidak penting sama sekali.
Lightness Races in Orbit
17

Melihat jawaban dan komentar di sini, tampaknya ada kesepakatan umum bahwa sintaks yang dipermasalahkan dapat membingungkan bagi pemula. Sebagian besar dari mereka mengusulkan sesuatu seperti ini:

  • Sebelum menunjukkan kode apa pun, gunakan diagram, sketsa, atau animasi untuk menggambarkan cara kerja pointer.
  • Saat menyajikan sintaksis, jelaskan dua peran simbol asterisk yang berbeda . Banyak tutorial yang hilang atau menghindari bagian itu. Terjadi kebingungan ("Ketika Anda memecah deklarasi pointer yang diinisialisasi ke dalam deklarasi dan tugas selanjutnya, Anda harus ingat untuk menghapus *" - comp.lang.c FAQ ) Saya berharap untuk menemukan pendekatan alternatif, tapi saya rasa ini adalah cara untuk pergi.

Anda dapat menulis int* baralih-alih int *barmenyoroti perbedaannya. Ini berarti Anda tidak akan mengikuti pendekatan K&R "declaration mimics use", tetapi pendekatan Stroustrup C ++ :

Kami tidak menyatakan *barsebagai bilangan bulat. Kami menyatakan barsebagai int*. Jika kita ingin menginisialisasi variabel yang baru dibuat di baris yang sama, jelas kita sedang berurusan bar, bukan *bar.int* bar = &foo;

Kekurangannya:

  • Anda harus memperingatkan siswa Anda tentang masalah deklarasi multi-pointer ( int* foo, barvs int *foo, *bar).
  • Anda harus mempersiapkan mereka untuk dunia yang terluka . Banyak programmer ingin melihat tanda bintang yang berdekatan dengan nama variabel, dan mereka akan berusaha keras untuk membenarkan gaya mereka. Dan banyak panduan gaya menerapkan notasi ini secara eksplisit (gaya pengkodean kernel Linux, NASA C Style Guide, dll.).

Sunting: Pendekatan berbeda yang telah disarankan, adalah mengikuti cara K&R "meniru", tetapi tanpa sintaks "steno" (lihat di sini ). Segera setelah Anda menghilangkan melakukan deklarasi dan penugasan di baris yang sama , semuanya akan terlihat jauh lebih masuk akal.

Namun, cepat atau lambat siswa harus berurusan dengan pointer sebagai argumen fungsi. Dan pointer sebagai tipe pengembalian. Dan petunjuk fungsi. Anda harus menjelaskan perbedaan antara int *func();dan int (*func)();. Saya pikir cepat atau lambat segalanya akan berantakan. Dan mungkin lebih cepat lebih baik daripada nanti.

armin
sumber
16

Ada alasan mengapa gaya K&R nikmat int *pdan gaya gaya Stroustrup int* p; keduanya valid (dan berarti hal yang sama) di setiap bahasa, tetapi seperti yang dikatakan Stroustrup:

Pilihan antara "int * p;" dan "int * p;" ini bukan tentang benar dan salah, tetapi tentang gaya dan penekanan. C menekankan ekspresi; deklarasi sering dianggap sedikit lebih dari sekadar kejahatan yang diperlukan. C ++, di sisi lain, memiliki penekanan besar pada jenis.

Sekarang, karena Anda mencoba mengajar C di sini, itu akan menyarankan Anda harus lebih menekankan ekspresi jenis itu, tetapi beberapa orang dapat lebih mudah grok satu penekanan lebih cepat daripada yang lain, dan itu tentang mereka daripada bahasa.

Oleh karena itu beberapa orang akan merasa lebih mudah untuk memulai dengan gagasan bahwa an int*adalah hal yang berbeda daripada an intdan pergi dari sana.

Jika seseorang cepat grok cara untuk melihat hal itu yang menggunakan int* baruntuk memiliki barsebagai hal yang tidak int, tapi pointer ke int, maka mereka akan segera melihat bahwa *barini melakukan sesuatu untuk bar, dan sisanya akan mengikuti. Setelah Anda selesai melakukannya nanti Anda bisa menjelaskan mengapa C coder cenderung lebih suka int *bar.

Atau tidak. Jika ada satu cara di mana setiap orang pertama kali memahami konsep itu, Anda tidak akan memiliki masalah sejak awal, dan cara terbaik untuk menjelaskannya kepada satu orang tidak akan selalu menjadi cara terbaik untuk menjelaskannya kepada orang lain.

Jon Hanna
sumber
1
Saya suka argumen Stroustrup, tetapi saya heran mengapa ia memilih & simbol untuk menunjukkan referensi - kemungkinan perangkap lain.
armin
1
@abw Saya pikir dia melihat simetri jika kita bisa melakukannya int* p = &amaka kita bisa melakukannya int* r = *p. Saya cukup yakin dia meliputnya di Desain dan Evolusi C ++ , tapi itu sudah lama sejak saya membacanya, dan saya dengan bodohnya mencondongkan salinan saya ke seseorang.
Jon Hanna
3
Saya kira maksud Anda int& r = *p. Dan saya yakin peminjam masih mencoba untuk mencerna buku itu.
armin
@abw, ya itu yang saya maksud. Sayangnya kesalahan ketik dalam komentar tidak menimbulkan kesalahan kompilasi. Buku ini sebenarnya cukup cepat dibaca.
Jon Hanna
4
Salah satu alasan saya mendukung sintaksis Pascal (seperti yang populer diperluas) daripada C adalah yang Var A, B: ^Integer;membuat jelas bahwa tipe "pointer to integer" berlaku untuk keduanya Adan B. Menggunakan K&Rgaya int *a, *bjuga bisa diterapkan; tetapi deklarasi seperti int* a,b;, bagaimanapun, terlihat seolah-olah adan bkeduanya dinyatakan sebagai int*, tetapi dalam kenyataannya menyatakan asebagai int*dan bsebagai int.
supercat
9

tl; dr:

T: Bagaimana cara menjelaskan pointer C (deklarasi vs operator unary) kepada seorang pemula?

A: jangan. Jelaskan pointer ke pemula, dan tunjukkan kepada mereka bagaimana untuk mewakili konsep pointer mereka dalam sintaks C setelah.


Saya baru-baru ini senang menjelaskan pointer ke pemula pemrograman C dan menemukan kesulitan berikut.

Sintaksis IMO the C tidak buruk, tetapi tidak bagus juga: itu bukan halangan yang bagus jika Anda sudah mengerti petunjuk, atau bantuan apa pun dalam mempelajarinya.

Oleh karena itu: mulailah dengan menjelaskan petunjuk, dan pastikan mereka benar-benar memahaminya:

  • Jelaskan dengan diagram kotak-dan-panah. Anda dapat melakukannya tanpa alamat hex, jika tidak relevan, cukup tunjukkan panah yang menunjuk ke kotak lain, atau ke beberapa simbol nul.

  • Jelaskan dengan pseudocode: cukup tulis alamat foo dan nilai yang disimpan di bar .

  • Kemudian, ketika pemula Anda mengerti apa itu pointer, dan mengapa, dan bagaimana menggunakannya; lalu perlihatkan pemetaan ke sintaks C.

Saya menduga alasan mengapa teks K&R tidak memberikan model konseptual adalah bahwa mereka sudah memahami petunjuk , dan mungkin mengasumsikan setiap programmer kompeten lainnya pada saat itu juga melakukannya. Mnemonic hanyalah pengingat pemetaan dari konsep yang dipahami dengan baik, ke sintaksis.

Tak berguna
sumber
Memang; Mulailah dengan teori terlebih dahulu, sintaks datang kemudian (dan tidak penting). Perhatikan bahwa teori penggunaan memori tidak tergantung pada bahasa. Model kotak-dan-panah ini akan membantu Anda dengan tugas dalam bahasa pemrograman apa pun.
oɔɯǝɹ
Lihat di sini untuk beberapa contoh (meskipun google juga akan membantu) eskimo.com/~scs/cclass/notes/sx10a.html
oɔɯǝɹ
7

Masalah ini agak membingungkan ketika mulai belajar C.

Berikut adalah prinsip-prinsip dasar yang mungkin membantu Anda memulai:

  1. Hanya ada beberapa tipe dasar di C:

    • char: nilai integer dengan ukuran 1 byte.

    • short: nilai integer dengan ukuran 2 byte.

    • long: nilai integer dengan ukuran 4 byte.

    • long long: nilai integer dengan ukuran 8 byte.

    • float: nilai non-integer dengan ukuran 4 byte.

    • double: nilai non-integer dengan ukuran 8 byte.

    Perhatikan bahwa ukuran setiap jenis umumnya ditentukan oleh kompiler dan bukan oleh standar.

    Jenis integer short, longdan long longbiasanya diikuti oleh int.

    Ini bukan keharusan, namun, dan Anda dapat menggunakannya tanpa int.

    Atau, Anda bisa saja menyatakan int, tetapi itu mungkin ditafsirkan berbeda oleh kompiler yang berbeda.

    Jadi untuk meringkas ini:

    • shortsama short inttetapi tidak harus sama dengan int.

    • longsama long inttetapi tidak harus sama dengan int.

    • long longsama long long inttetapi tidak harus sama dengan int.

    • Pada kompiler yang diberikan, intapakah short intatau long intatau long long int.

  2. Jika Anda mendeklarasikan variabel dari beberapa jenis, maka Anda juga dapat mendeklarasikan variabel lain yang menunjuk padanya.

    Sebagai contoh:

    int a;

    int* b = &a;

    Jadi intinya, untuk setiap tipe dasar, kami juga memiliki tipe pointer yang sesuai.

    Misalnya: shortdan short*.

    Ada dua cara untuk "melihat" variabel b (itulah yang mungkin membingungkan kebanyakan pemula) :

    • Anda dapat mempertimbangkan bsebagai variabel tipe int*.

    • Anda dapat mempertimbangkan *bsebagai variabel tipe int.

    Oleh karena itu, beberapa orang akan menyatakan int* b, sedangkan yang lain akan menyatakan int *b.

    Tetapi faktanya adalah bahwa kedua deklarasi ini identik (spasi tidak berarti).

    Anda bisa menggunakan bsebagai penunjuk ke nilai integer, atau *bsebagai nilai integer runcing yang sebenarnya.

    Anda bisa mendapatkan (baca) nilai runcing: int c = *b.

    Dan Anda dapat mengatur (write) nilai runcing: *b = 5.

  3. Pointer dapat menunjuk ke alamat memori apa pun, dan tidak hanya ke alamat beberapa variabel yang telah Anda nyatakan sebelumnya. Namun, Anda harus berhati-hati saat menggunakan pointer untuk mendapatkan atau mengatur nilai yang terletak di alamat memori yang ditunjuk.

    Sebagai contoh:

    int* a = (int*)0x8000000;

    Di sini, kami memiliki variabel yang amenunjuk ke alamat memori 0x8000000.

    Jika alamat memori ini tidak dipetakan dalam ruang memori program Anda, maka setiap operasi membaca atau menulis menggunakan *akemungkinan besar akan menyebabkan program Anda macet, karena pelanggaran akses memori.

    Anda dapat dengan aman mengubah nilai a, tetapi Anda harus sangat berhati-hati mengubah nilainya *a.

  4. Ketik void*luar biasa karena tidak memiliki "tipe nilai" yang sesuai yang dapat digunakan (yaitu, Anda tidak dapat mendeklarasikan void a). Tipe ini hanya digunakan sebagai penunjuk umum ke alamat memori, tanpa menentukan jenis data yang berada di alamat itu.

barak manos
sumber
7

Mungkin melangkah sedikit saja membuatnya lebih mudah:

#include <stdio.h>

int main()
{
    int foo = 1;
    int *bar = &foo;
    printf("%i\n", foo);
    printf("%p\n", &foo);
    printf("%p\n", (void *)&foo);
    printf("%p\n", &bar);
    printf("%p\n", bar);
    printf("%i\n", *bar);
    return 0;
}

Mintalah mereka memberi tahu Anda apa yang mereka harapkan dari output di setiap baris, kemudian minta mereka menjalankan program dan melihat apa yang muncul. Jelaskan pertanyaan mereka (versi telanjang di sana pasti akan meminta beberapa - tetapi Anda dapat khawatir tentang gaya, ketegasan dan portabilitas nanti). Kemudian, sebelum pikiran mereka menjadi bubur karena terlalu banyak berpikir atau mereka menjadi zombie setelah makan siang, tulislah sebuah fungsi yang mengambil nilai, dan fungsi yang sama yang mengambil pointer.

Dalam pengalaman saya, itu mengatasi bahwa "mengapa ini dicetak seperti itu?" punuk, dan kemudian segera menunjukkan mengapa hal ini berguna dalam parameter fungsi dengan memainkan langsung (sebagai pembuka untuk beberapa materi dasar K&R seperti parsing string / pemrosesan array) yang membuat pelajaran tidak hanya masuk akal tetapi tetap.

Langkah selanjutnya adalah membuat mereka menjelaskan kepada Anda bagaimana i[0]hubungannya &i. Jika mereka bisa melakukan itu, mereka tidak akan melupakannya dan Anda dapat mulai berbicara tentang struct, bahkan sedikit di muka, hanya saja begitu meresap.

Rekomendasi di atas tentang kotak dan panah juga baik, tetapi juga dapat berakhir menyimpang ke dalam diskusi penuh tentang bagaimana memori bekerja - yang merupakan pembicaraan yang harus terjadi di beberapa titik, tetapi dapat mengalihkan perhatian dari titik segera di tangan : bagaimana menafsirkan notasi pointer dalam C.

zxq9
sumber
Ini latihan yang bagus. Tetapi masalah yang ingin saya kemukakan adalah masalah sintaksis spesifik yang mungkin berdampak pada model mental yang dibangun siswa. Pertimbangkan ini: int foo = 1;. Sekarang ini adalah OK: int *bar; *bar = foo;. Ini tidak apa-apa:int *bar = foo;
armin
1
@ ABW Satu-satunya hal yang masuk akal adalah apa pun yang siswa katakan pada diri mereka sendiri. Itu berarti "lihat satu, lakukan satu, ajarkan satu". Anda tidak dapat melindungi dari atau memprediksi sintaks atau gaya apa yang akan mereka lihat di sana di hutan (bahkan repo lama Anda!), Jadi Anda harus menunjukkan permutasi yang cukup sehingga konsep dasar dipahami terlepas dari gaya - dan kemudian mulailah mengajari mereka mengapa gaya-gaya tertentu digunakan. Seperti mengajar bahasa Inggris: ekspresi dasar, idiom, gaya, gaya tertentu dalam konteks tertentu. Sayangnya tidak mudah. Bagaimanapun, Semoga Sukses!
zxq9
6

Jenis ekspresinya *bar adalah int; dengan demikian, tipe variabel (dan ekspresi) baradalah int *. Karena variabel memiliki tipe pointer, penginisialisasi juga harus memiliki tipe pointer.

Ada ketidakkonsistenan antara inisialisasi variabel penunjuk dan penugasan; itu hanya sesuatu yang harus dipelajari dengan cara yang sulit.

John Bode
sumber
3
Melihat jawaban di sini saya merasa bahwa banyak programmer yang berpengalaman bahkan tidak bisa melihat masalah lagi. Saya kira itu adalah produk sampingan dari "belajar untuk hidup dengan ketidakkonsistenan".
armin
3
@ abw: aturan inisialisasi berbeda dari aturan untuk tugas; untuk tipe skalar aritmatika perbedaan dapat diabaikan, tetapi mereka penting untuk tipe pointer dan agregat. Itu adalah sesuatu yang perlu Anda jelaskan bersama dengan yang lainnya.
John Bode
5

Saya lebih suka membacanya sebagai yang pertama *berlaku untuk intlebih dari bar.

int  foo = 1;           // foo is an integer (int) with the value 1
int* bar = &foo;        // bar is a pointer on an integer (int*). it points on foo. 
                        // bar value is foo address
                        // *bar value is foo value = 1

printf("%p\n", &foo);   // print the address of foo
printf("%p\n", bar);    // print the address of foo
printf("%i\n", foo);    // print foo value
printf("%i\n", *bar);   // print foo value
Grorel
sumber
2
Maka Anda harus menjelaskan mengapa int* a, btidak melakukan apa yang mereka pikirkan.
Pharap
4
Benar, tapi saya tidak berpikir itu int* a,bharus digunakan sama sekali. Untuk kelayakan yang lebih baik, pembaruan, dll ... seharusnya hanya ada satu deklarasi variabel per baris dan tidak pernah lagi. Ini sesuatu untuk dijelaskan kepada pemula juga, bahkan jika kompiler dapat menanganinya.
Grorel
Itu pendapat satu orang. Ada jutaan programmer di luar sana yang benar-benar baik-baik saja dengan menyatakan lebih dari satu variabel per baris dan melakukannya setiap hari sebagai bagian dari pekerjaan mereka. Anda tidak dapat menyembunyikan siswa dari cara-cara alternatif dalam melakukan sesuatu, lebih baik menunjukkan kepada mereka semua alternatif dan membiarkan mereka memutuskan ke arah mana mereka ingin melakukan sesuatu karena jika mereka pernah dipekerjakan mereka akan diharapkan untuk mengikuti gaya tertentu yang mereka mungkin merasa nyaman atau tidak. Untuk seorang programmer, fleksibilitas adalah sifat yang sangat baik untuk dimiliki.
Pharap
1
Saya setuju dengan @grorel. Lebih mudah untuk menganggapnya *sebagai bagian dari tipenya, dan hanya mencegah int* a, b. Kecuali jika Anda lebih suka mengatakan bahwa itu *aadalah tipe intdaripada apointer ke int...
Kevin Ushey
@grorel benar: int *a, b;tidak boleh digunakan. Mendeklarasikan dua variabel dengan tipe berbeda dalam pernyataan yang sama merupakan praktik yang sangat buruk dan merupakan kandidat yang kuat untuk masalah pemeliharaan. Mungkin berbeda bagi kita yang bekerja di bidang tertanam di mana an int*dan an intsering berbeda ukuran dan kadang-kadang disimpan di lokasi memori yang sama sekali berbeda. Ini adalah salah satu dari banyak aspek bahasa C yang paling baik diajarkan karena 'diizinkan, tetapi jangan lakukan itu.'
Evil Dog Pie
5
int *bar = &foo;

Question 1: Apa itu bar?

Ans: Ini adalah variabel pointer (untuk mengetik int). Pointer harus menunjuk ke beberapa lokasi memori yang valid dan kemudian harus direferensikan (* bar) menggunakan operator unary *untuk membaca nilai yang disimpan di lokasi itu.

Question 2: Apa itu &foo?

Ans: foo adalah variabel bertipe int.yang disimpan di beberapa lokasi memori yang valid dan lokasi itu kita dapatkan dari operator &jadi sekarang yang kita miliki adalah beberapa lokasi memori yang valid &foo.

Jadi keduanya disatukan yaitu apa yang dibutuhkan pointer adalah lokasi memori yang valid dan yang didapat &foosehingga inisialisasi yang baik.

Sekarang pointer barmenunjuk ke lokasi memori yang valid dan nilai yang tersimpan di dalamnya bisa didapat yaitu dereferencing yaitu*bar

Gopi
sumber
5

Anda harus menunjukkan seorang pemula yang * memiliki arti berbeda dalam deklarasi dan ungkapan. Seperti yang Anda ketahui, * dalam ekspresi adalah operator unary, dan * Dalam deklarasi bukan operator dan hanya semacam sintaks yang menggabungkan dengan tipe untuk membuat kompiler tahu bahwa itu adalah tipe pointer. lebih baik mengatakan seorang pemula, "* memiliki arti yang berbeda. Untuk memahami makna *, Anda harus menemukan di mana * digunakan"

Yongkil Kwon
sumber
4

Saya pikir iblis ada di ruang angkasa.

Saya akan menulis (tidak hanya untuk pemula, tetapi untuk diri saya juga): int * bar = & foo; bukannya int * bar = & foo;

ini harus membuktikan apa hubungan antara sintaks dan semantik

rpaulin56
sumber
4

Sudah dicatat bahwa * memiliki banyak peran.

Ada ide sederhana lain yang dapat membantu pemula untuk memahami hal-hal:

Pikirkan bahwa "=" memiliki banyak peran juga.

Ketika penugasan digunakan pada baris yang sama dengan deklarasi, anggap itu sebagai panggilan konstruktor, bukan penugasan sewenang-wenang.

Ketika kamu melihat:

int *bar = &foo;

Pikirkan bahwa itu hampir setara dengan:

int *bar(&foo);

Tanda kurung lebih diutamakan daripada tanda bintang, jadi "& foo" lebih mudah secara intuitif dikaitkan dengan "bilah" daripada "* bilah".

morfizm
sumber
4

Saya melihat pertanyaan ini beberapa hari yang lalu, dan kemudian kebetulan membaca penjelasan deklarasi tipe Go di Go Blog . Ini dimulai dengan memberikan akun deklarasi tipe C, yang sepertinya merupakan sumber yang berguna untuk ditambahkan ke utas ini, meskipun saya pikir ada jawaban yang lebih lengkap yang sudah diberikan.

C mengambil pendekatan yang tidak biasa dan pintar untuk sintaksis deklarasi. Alih-alih mendeskripsikan tipe dengan sintaks khusus, orang menulis ekspresi yang melibatkan item yang dideklarasikan, dan menyatakan tipe apa yang akan dimiliki ekspresi tersebut. Jadi

int x;

mendeklarasikan x sebagai int: ekspresi 'x' akan memiliki tipe int. Secara umum, untuk mengetahui cara menulis tipe variabel baru, tulis ekspresi yang melibatkan variabel yang mengevaluasi ke tipe dasar, lalu letakkan tipe dasar di sebelah kiri dan ekspresi di sebelah kanan.

Demikianlah deklarasi tersebut

int *p;
int a[3];

menyatakan bahwa p adalah pointer ke int karena '* p' memiliki tipe int, dan bahwa a adalah array int karena a [3] (mengabaikan nilai indeks tertentu, yang dihukum sebagai ukuran array) bertipe int.

(Selanjutnya menjelaskan bagaimana memperluas pemahaman ini ke pointer fungsi dll)

Ini adalah cara yang belum pernah saya pikirkan sebelumnya, tetapi sepertinya cara yang cukup mudah untuk menghitung kelebihan sintaks.

Andy Turner
sumber
3

Jika masalahnya adalah sintaks, mungkin bermanfaat untuk menunjukkan kode yang setara dengan templat / using.

template<typename T>
using ptr = T*;

Ini kemudian dapat digunakan sebagai

ptr<int> bar = &foo;

Setelah itu, bandingkan sintaks normal / C dengan pendekatan C ++ ini saja. Ini juga berguna untuk menjelaskan pointer const.

MI3Guy
sumber
2
Untuk pemula akan jauh lebih membingungkan.
Karsten
Meskipun saya adalah bahwa Anda tidak akan menunjukkan definisi ptr. Cukup gunakan untuk deklarasi pointer.
MI3Guy
3

Sumber kebingungan muncul dari fakta bahwa *simbol dapat memiliki makna yang berbeda dalam C, tergantung pada fakta di mana simbol digunakan. Untuk menjelaskan pointer ke pemula, makna *simbol dalam konteks yang berbeda harus dijelaskan.

Dalam deklarasi

int *bar = &foo;  

yang *simbol adalah bukan operator tipuan . Sebagai gantinya, ini membantu untuk menentukan jenis barmenginformasikan kompiler yang barmerupakan pointer keint . Di sisi lain, ketika muncul dalam pernyataan *simbol (bila digunakan sebagai operator unary ) melakukan tipuan. Karena itu, pernyataannya

*bar = &foo;

akan salah karena memberikan alamat fooke objek yang barmenunjuk, bukan ke bardirinya sendiri.

haccks
sumber
3

"Mungkin menulisnya sebagai bilah int * membuatnya lebih jelas bahwa bintang itu sebenarnya bagian dari tipe, bukan bagian dari pengenal." Jadi saya lakukan. Dan saya katakan, itu agak mirip dengan Type, tetapi hanya untuk satu nama pointer.

"Tentu saja ini membuatmu mengalami masalah yang berbeda dengan hal-hal yang tidak intuitif seperti int * a, b."

Павел Бивойно
sumber
2

Di sini Anda harus menggunakan, memahami dan menjelaskan logika kompiler, bukan logika manusia (saya tahu, Anda adalah manusia, tetapi di sini Anda harus meniru komputer ...).

Ketika Anda menulis

int *bar = &foo;

kompiler mengelompokkan itu sebagai

{ int * } bar = &foo;

Yaitu: di sini adalah variabel baru, namanya bar, tipenya adalah pointer ke int, dan nilai awalnya adalah &foo.

Dan Anda harus menambahkan: yang =Menandakan di atas sebuah inisialisasi bukan kepura-puraan, sedangkan di ungkapan berikut *bar = 2;ini adalah afirmasi

Edit per komentar:

Hati-hati: dalam hal beberapa deklarasi *hanya terkait dengan variabel berikut:

int *bar = &foo, b = 2;

bilah adalah pointer ke int yang diinisialisasi dengan alamat foo, b adalah int yang diinisialisasi ke 2, dan dalam

int *bar=&foo, **p = &bar;

bilah di penunjuk diam ke int, dan p adalah penunjuk ke penunjuk ke int yang diinisialisasi ke alamat atau bilah.

Serge Ballesta
sumber
2
Sebenarnya kompiler tidak mengelompokkannya seperti itu: int* a, b;menyatakan a untuk menjadi pointer ke int, tetapi b menjadi int. The *simbol hanya memiliki dua makna yang berbeda: Dalam deklarasi, itu menunjukkan jenis pointer, dan ekspresi itu adalah operator dereference unary.
tmlen
@ HTMLen: Apa yang saya maksudkan adalah bahwa dalam inisialisasi, *in rattached ke tipe, sehingga pointer diinisialisasi sedangkan dalam suatu afeksi nilai rujukan dipengaruhi. Tapi setidaknya Anda memberi saya topi yang bagus :-)
Serge Ballesta
0

Pointer pada dasarnya bukan indikasi array. Pemula dengan mudah berpikir bahwa pointer terlihat seperti array. sebagian besar contoh string menggunakan

"char * pstr" bentuknya mirip

"char str [80]"

Tapi, hal-hal penting, Pointer diperlakukan hanya sebagai integer di level bawah dari kompiler.

Mari kita lihat contoh ::

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

int main(int argc, char **argv, char **env)
{
    char str[] = "This is Pointer examples!"; // if we assume str[] is located in 0x80001000 address

    char *pstr0 = str;   // or this will be using with
    // or
    char *pstr1 = &str[0];

    unsigned int straddr = (unsigned int)pstr0;

    printf("Pointer examples: pstr0 = %08x\n", pstr0);
    printf("Pointer examples: &str[0] = %08x\n", &str[0]);
    printf("Pointer examples: str = %08x\n", str);
    printf("Pointer examples: straddr = %08x\n", straddr);
    printf("Pointer examples: str[0] = %c\n", str[0]);

    return 0;
}

Hasil akan menyukai 0x2a6b7ed0 ini adalah alamat str []

~/work/test_c_code$ ./testptr
Pointer examples: pstr0 = 2a6b7ed0
Pointer examples: &str[0] = 2a6b7ed0
Pointer examples: str = 2a6b7ed0
Pointer examples: straddr = 2a6b7ed0
Pointer examples: str[0] = T

Jadi, pada dasarnya, perlu diingat Pointer adalah semacam Integer. menyajikan Alamat.

cpplover - Slw Essencial
sumber
-1

Saya akan menjelaskan bahwa int adalah objek, seperti mengapung dll. Sebuah pointer adalah jenis objek yang nilainya mewakili alamat dalam memori (karenanya mengapa pointer default ke NULL).

Saat pertama kali mendeklarasikan pointer, Anda menggunakan sintaks type-pointer-name. Itu dibaca sebagai "integer-pointer bernama nama yang dapat menunjuk ke alamat objek integer". Kami hanya menggunakan sintaks ini selama deklerasi, mirip dengan bagaimana kami mendeklarasikan int sebagai 'int num1' tetapi kami hanya menggunakan 'num1' ketika kami ingin menggunakan variabel itu, bukan 'int num1'.

int x = 5; // objek integer dengan nilai 5

int * ptr; // bilangan bulat dengan nilai NULL secara default

Untuk membuat titik penunjuk ke alamat suatu objek, kami menggunakan simbol '&' yang dapat dibaca sebagai "alamat".

ptr = & x; // sekarang nilai adalah alamat 'x'

Karena penunjuk hanya alamat dari objek, untuk mendapatkan nilai aktual yang dipegang di alamat itu, kita harus menggunakan simbol '*' yang bila digunakan sebelum penunjuk berarti "nilai pada alamat yang ditunjuk oleh".

std :: cout << * ptr; // cetak nilai di alamat

Anda dapat menjelaskan secara singkat bahwa ' ' adalah 'operator' yang mengembalikan hasil berbeda dengan berbagai jenis objek. Saat digunakan dengan pointer, operator ' ' tidak berarti "dikalikan dengan" lagi.

Ini membantu untuk menggambar diagram yang menunjukkan bagaimana variabel memiliki nama dan nilai dan pointer memiliki alamat (nama) dan nilai dan menunjukkan bahwa nilai pointer akan menjadi alamat int.

pengguna2796283
sumber
-1

Pointer hanyalah variabel yang digunakan untuk menyimpan alamat.

Memori dalam komputer terdiri dari byte (byte terdiri dari 8 bit) yang disusun secara berurutan. Setiap byte memiliki nomor yang terkait dengannya seperti indeks atau subskrip dalam array, yang disebut alamat byte. Alamat byte dimulai dari 0 hingga kurang dari ukuran memori. Misalnya, katakan dalam RAM 64MB, ada 64 * 2 ^ 20 = 67108864 byte. Oleh karena itu alamat byte ini akan mulai dari 0 hingga 67108863.

masukkan deskripsi gambar di sini

Mari kita lihat apa yang terjadi ketika Anda mendeklarasikan variabel.

tanda int;

Seperti yang kita ketahui bahwa suatu int menempati 4 byte data (dengan asumsi kita menggunakan kompiler 32-bit), maka kompiler menyimpan 4 byte berturut-turut dari memori untuk menyimpan nilai integer. Alamat byte pertama dari 4 byte yang dialokasikan dikenal sebagai alamat dari tanda variabel. Katakanlah alamat 4 byte berturut-turut adalah 5004, 5005, 5006 dan 5007 maka alamat dari tanda variabel adalah 5004. masukkan deskripsi gambar di sini

Mendeklarasikan variabel pointer

Seperti yang sudah dikatakan pointer adalah variabel yang menyimpan alamat memori. Sama seperti variabel lain yang Anda perlu mendeklarasikan variabel pointer terlebih dahulu sebelum Anda dapat menggunakannya. Inilah cara Anda dapat mendeklarasikan variabel pointer.

Sintaksis: data_type *pointer_name;

data_type adalah tipe dari pointer (juga dikenal sebagai tipe dasar dari pointer). pointer_name adalah nama variabel, yang bisa berupa pengidentifikasi C apa pun yang valid.

Mari kita ambil beberapa contoh:

int *ip;

float *fp;

int * ip berarti bahwa ip adalah variabel penunjuk yang mampu menunjuk ke variabel tipe int. Dengan kata lain, variabel pointer ip dapat menyimpan alamat variabel tipe saja. Demikian pula, variabel pointer fp hanya dapat menyimpan alamat variabel tipe float. Tipe variabel (juga dikenal sebagai tipe dasar) ip adalah pointer ke int dan tipe fp adalah pointer ke float. Variabel pointer tipe pointer ke int dapat secara simbolis direpresentasikan sebagai (int *). Demikian pula, variabel pointer tipe pointer ke float dapat direpresentasikan sebagai (float *)

Setelah mendeklarasikan variabel pointer, langkah selanjutnya adalah menetapkan beberapa alamat memori yang valid untuknya. Anda seharusnya tidak pernah menggunakan variabel pointer tanpa menetapkan beberapa alamat memori yang valid untuk itu, karena tepat setelah deklarasi itu berisi nilai sampah dan mungkin menunjuk ke mana saja di memori. Penggunaan pointer yang tidak ditetapkan dapat memberikan hasil yang tidak terduga. Bahkan dapat menyebabkan program macet.

int *ip, i = 10;
float *fp, f = 12.2;

ip = &i;
fp = &f;

Sumber: thecguru sejauh ini merupakan penjelasan paling sederhana namun terperinci yang pernah saya temukan.

Cody
sumber