Operator dot ( .
) digunakan untuk mengakses anggota struct, sedangkan operator panah ( ->
) di C digunakan untuk mengakses anggota struct yang dirujuk oleh pointer yang dimaksud.
Pointer itu sendiri tidak memiliki anggota yang dapat diakses dengan operator titik (sebenarnya hanya angka yang menggambarkan lokasi dalam memori virtual sehingga tidak memiliki anggota). Jadi, tidak akan ada ambiguitas jika kita hanya mendefinisikan operator dot untuk secara otomatis melakukan dereferensi pointer jika digunakan pada sebuah pointer (informasi yang diketahui oleh kompiler pada waktu kompilasi afaik).
Jadi mengapa para pembuat bahasa memutuskan untuk membuat hal-hal lebih rumit dengan menambahkan operator yang tampaknya tidak perlu ini? Apa keputusan desain besar?
sumber
.
operator memiliki prioritas lebih tinggi daripada*
operator? Jika tidak, kita dapat memiliki * ptr.member dan var.member.Jawaban:
Saya akan menginterpretasikan pertanyaan Anda sebagai dua pertanyaan: 1) mengapa
->
ada, dan 2) mengapa.
tidak secara otomatis mengubah referensi pointer. Jawaban untuk kedua pertanyaan memiliki akar sejarah.Mengapa
->
bahkan ada?Dalam salah satu versi bahasa C yang paling pertama (yang akan saya rujuk sebagai CRM untuk " Manual Referensi C ", yang datang dengan Unix Edisi ke-6 pada bulan Mei 1975), operator
->
memiliki makna yang sangat eksklusif, tidak identik dengan*
dan.
kombinasiBahasa C yang dijelaskan oleh CRM sangat berbeda dari bahasa C modern dalam banyak hal. Dalam CRM struct, anggota menerapkan konsep global byte offset , yang dapat ditambahkan ke nilai alamat apa pun tanpa batasan jenis. Yaitu semua nama semua anggota struct memiliki makna global independen (dan, oleh karena itu, harus unik). Misalnya Anda bisa mendeklarasikan
dan nama
a
akan berarti offset 0, sedangkan namab
akan mewakili offset 2 (dengan asumsiint
tipe ukuran 2 dan tanpa bantalan). Bahasa mengharuskan semua anggota semua struct di unit terjemahan memiliki nama yang unik atau memiliki nilai offset yang sama. Misalnya dalam unit terjemahan yang sama Anda juga dapat mendeklarasikandan itu akan baik-baik saja, karena namanya
a
akan secara konsisten berarti offset 0. Tapi deklarasi tambahan iniakan secara resmi tidak valid, karena berusaha "mendefinisikan kembali"
a
sebagai offset 2 danb
sebagai offset 0.Dan di sinilah
->
operator masuk. Karena setiap nama anggota struct memiliki makna global mandiri, bahasa mendukung ekspresi seperti iniTugas pertama ditafsirkan oleh kompiler sebagai "ambil alamat
5
, tambahkan offset2
ke sana dan tetapkan42
keint
nilai di alamat yang dihasilkan". Yaitu di atas akan menugaskan42
untukint
nilai di alamat7
. Perhatikan bahwa penggunaan->
ini tidak peduli dengan jenis ekspresi di sisi kiri. Sisi kiri ditafsirkan sebagai nilai numerik alamat (baik itu pointer atau integer).Semacam ini tipu daya itu tidak mungkin dengan
*
dan.
kombinasi. Anda tidak bisa melakukannyakarena
*i
sudah merupakan ekspresi yang tidak valid. The*
operator, karena terpisah dari.
, memberlakukan persyaratan jenis yang lebih ketat pada operand. Untuk memberikan kemampuan untuk mengatasi keterbatasan ini, CRM memperkenalkan->
operator, yang independen dari jenis operan di sebelah kiri.Seperti yang disebutkan Keith dalam komentar, perbedaan antara
->
dan*
+.
kombinasi inilah yang disebut CRM sebagai "pelonggaran persyaratan" dalam 7.1.8: Kecuali untuk pelonggaran persyaratan yangE1
bertipe pointer, ekspresiE1−>MOS
persis sama dengan(*E1).MOS
Kemudian, di K&R C banyak fitur yang awalnya dijelaskan dalam CRM secara signifikan ulang. Gagasan "struct member sebagai global offset identifier" sepenuhnya dihapus. Dan fungsi
->
operator menjadi sepenuhnya identik dengan fungsi*
dan.
kombinasi.Mengapa
.
dereference pointer tidak bisa secara otomatis?Sekali lagi, dalam versi CRM bahasa operan kiri
.
Operator diperlukan untuk menjadi lvalue . Itulah satu - satunya persyaratan yang dikenakan pada operan itu (dan itulah yang membuatnya berbeda dari->
, seperti dijelaskan di atas). Perhatikan bahwa CRM tidak memerlukan operan kiri.
untuk memiliki tipe struct. Itu hanya diperlukan untuk menjadi nilai, nilai apa pun . Ini berarti bahwa dalam versi CRM C Anda dapat menulis kode seperti iniDalam hal ini kompiler akan menulis
55
keint
nilai yang diposisikan pada byte-offset 2 di blok memori kontinu yang dikenal sebagaic
, meskipun tipestruct T
tidak memiliki bidang bernamab
. Kompiler tidak akan peduli dengan tipe yang sebenarnyac
sama sekali. Yang dipedulikannyac
hanyalah nilai tinggi: semacam blok memori yang bisa ditulis.Sekarang perhatikan bahwa jika Anda melakukan ini
kode akan dianggap valid (karena
s
juga merupakan lvalue) dan kompiler hanya akan mencoba untuk menulis data ke dalam pointers
itu sendiri , pada byte-offset 2. Tidak perlu dikatakan, hal-hal seperti ini dapat dengan mudah mengakibatkan memori overrun, tetapi bahasa tidak peduli dengan masalah seperti itu.Yaitu dalam versi bahasa ide yang Anda usulkan tentang operator overloading
.
untuk tipe pointer tidak akan berfungsi: operator.
sudah memiliki makna yang sangat spesifik ketika digunakan dengan pointer (dengan pointer nilai atau dengan nilai apa pun sama sekali). Fungsionalitasnya sangat aneh, tidak diragukan lagi. Tetapi itu ada di sana pada saat itu.Tentu saja, fungsi yang aneh ini bukan alasan yang sangat kuat untuk tidak memperkenalkan
.
operator kelebihan beban untuk pointer (seperti yang Anda sarankan) dalam versi ulang C - K&R C. Tapi itu belum dilakukan. Mungkin pada waktu itu ada beberapa kode lama yang ditulis dalam versi CRM C yang harus didukung.(URL untuk Manual Referensi C 1975 mungkin tidak stabil. Salinan lain, mungkin dengan beberapa perbedaan halus, ada di sini .)
sumber
*i
menjadi nilai dari beberapa tipe default (int?) Di alamat 5? Maka (* i) .b akan bekerja dengan cara yang sama.struct stat
) awali bidangnya (misalnya,st_mode
).Di luar alasan historis (baik dan sudah dilaporkan), ada juga sedikit masalah dengan prioritas operator: operator titik memiliki prioritas lebih tinggi daripada operator bintang, jadi jika Anda memiliki pointer yang berisi pointer ke struct yang berisi pointer ke struct ... Keduanya setara:
Tapi yang kedua jelas lebih mudah dibaca. Operator panah memiliki prioritas tertinggi (sama seperti titik) dan rekan kiri ke kanan. Saya pikir ini lebih jelas daripada menggunakan operator titik baik untuk pointer ke struct dan struct, karena kita tahu tipe dari ekspresi tanpa harus melihat pada deklarasi, yang bahkan bisa di file lain.
sumber
a.b.c.d
sebagai(*(*(*a).b).c).d
, membuat->
operator tidak berguna. Jadi versi OP (a.b.c.d
) sama-sama dapat dibaca (dibandingkan dengana->b->c->d
). Itu sebabnya jawaban Anda tidak menjawab pertanyaan OP.a.b.c.d
dana->b->c->d
sebagai dua hal yang sangat berbeda: Yang pertama adalah akses memori tunggal ke sub-objek bersarang (hanya ada satu objek memori tunggal dalam kasus ini ), yang kedua adalah tiga akses memori, mengejar pointer melalui empat objek yang berbeda. Itu perbedaan besar dalam tata letak memori, dan saya percaya bahwa C benar dalam membedakan kedua kasus ini dengan sangat jelas.C juga melakukan pekerjaan dengan baik karena tidak membuat sesuatu yang ambigu.
Tentu saja dot bisa kelebihan beban artinya dua hal, tetapi panah memastikan bahwa programmer tahu bahwa dia beroperasi pada pointer, seperti ketika kompiler tidak akan membiarkan Anda mencampur dua jenis yang tidak kompatibel.
sumber