Saya membaca dokumentasi dan saya terus-menerus menggelengkan kepala pada beberapa keputusan desain bahasa. Tetapi hal yang benar-benar membuat saya bingung adalah bagaimana array ditangani.
Saya bergegas ke taman bermain dan mencoba ini. Anda dapat mencobanya juga. Jadi contoh pertama:
var a = [1, 2, 3]
var b = a
a[1] = 42
a
b
Di sini a
dan b
keduanya [1, 42, 3]
, yang dapat saya terima. Array direferensikan - OK!
Sekarang lihat contoh ini:
var c = [1, 2, 3]
var d = c
c.append(42)
c
d
c
adalah [1, 2, 3, 42]
TAPI d
adalah [1, 2, 3]
. Yaitu, d
melihat perubahan pada contoh terakhir tetapi tidak melihatnya dalam contoh ini. Dokumentasi mengatakan itu karena panjangnya berubah.
Sekarang, bagaimana dengan yang ini:
var e = [1, 2, 3]
var f = e
e[0..2] = [4, 5]
e
f
e
adalah [4, 5, 3]
, yang keren. Sangat menyenangkan memiliki pengganti multi-indeks, tetapi f
MASIH tidak melihat perubahan meskipun panjangnya tidak berubah.
Jadi untuk meringkasnya, referensi umum ke array melihat perubahan jika Anda mengubah 1 elemen, tetapi jika Anda mengubah beberapa elemen atau menambahkan item, salinan dibuat.
Ini sepertinya desain yang sangat buruk bagi saya. Apakah saya benar dalam memikirkan ini? Apakah ada alasan saya tidak melihat mengapa array harus bertindak seperti ini?
EDIT : Array telah berubah dan sekarang memiliki semantik nilai. Jauh lebih waras!
std::shared_ptr
tidak memiliki versi non-atom, ada jawaban berdasarkan fakta, bukan pendapat (faktanya panitia mempertimbangkannya tetapi tidak menginginkannya karena berbagai alasan).Jawaban:
Perhatikan bahwa semantik dan sintaks array diubah dalam versi Xcode beta 3 ( posting blog ), jadi pertanyaannya tidak berlaku lagi. Jawaban berikut berlaku untuk beta 2:
Itu untuk alasan kinerja. Pada dasarnya, mereka mencoba untuk menghindari menyalin array selama mereka dapat (dan mengklaim "kinerja seperti C"). Mengutip buku bahasa :
Saya setuju bahwa ini agak membingungkan, tetapi setidaknya ada deskripsi yang jelas dan sederhana tentang cara kerjanya.
Bagian itu juga mencakup informasi tentang cara memastikan array secara unik direferensikan, cara memaksa-menyalin array, dan bagaimana memeriksa apakah dua array berbagi penyimpanan.
sumber
Dari dokumentasi resmi bahasa Swift :
Baca seluruh bagian Penugasan dan Salin Perilaku untuk Array dalam dokumentasi ini. Anda akan menemukan bahwa ketika Anda mengganti berbagai item dalam array maka array mengambil salinannya sendiri untuk semua item.
sumber
Perilaku telah berubah dengan Xcode 6 beta 3. Array bukan lagi tipe referensi dan memiliki mekanisme copy-on-write , artinya segera setelah Anda mengubah konten array dari satu atau variabel lain, array akan disalin dan hanya satu salinan akan diubah.
Jawaban lama:
Seperti yang telah ditunjukkan orang lain, Swift mencoba menghindari menyalin array jika memungkinkan, termasuk ketika mengubah nilai untuk indeks tunggal pada suatu waktu.
Jika Anda ingin memastikan bahwa variabel array (!) Unik, yaitu tidak dibagi dengan variabel lain, Anda dapat memanggil
unshare
metode. Ini menyalin array kecuali sudah hanya memiliki satu referensi. Tentu saja Anda juga dapat memanggilcopy
metode, yang akan selalu membuat salinan, tetapi berhenti berbagi lebih disukai untuk memastikan tidak ada variabel lain yang bertahan pada array yang sama.sumber
unshare()
metode itu tidak terdefinisi.Perilaku ini sangat mirip dengan
Array.Resize
metode di .NET. Untuk memahami apa yang terjadi, mungkin berguna untuk melihat sejarah.
token di C, C ++, Java, C #, dan Swift.Dalam C, struktur tidak lebih dari agregasi variabel. Menerapkannya
.
ke variabel tipe struktur akan mengakses variabel yang disimpan dalam struktur. Pointer ke objek tidak memiliki agregasi variabel, tetapi mengidentifikasi mereka. Jika seseorang memiliki pointer yang mengidentifikasi struktur,->
operator dapat digunakan untuk mengakses variabel yang disimpan dalam struktur yang diidentifikasi oleh pointer.Dalam C ++, struktur dan kelas tidak hanya variabel agregat, tetapi juga dapat melampirkan kode padanya. Menggunakan
.
untuk memanggil suatu metode akan pada variabel meminta metode itu untuk bertindak atas isi dari variabel itu sendiri ; menggunakan->
pada variabel yang mengidentifikasi objek akan meminta metode itu untuk bertindak atas objek yang diidentifikasi oleh variabel.Di Jawa, semua tipe variabel kustom hanya mengidentifikasi objek, dan memanggil metode pada variabel akan memberi tahu metode objek apa yang diidentifikasi oleh variabel. Variabel tidak dapat menampung segala jenis tipe data komposit secara langsung, juga tidak ada cara yang digunakan suatu metode untuk mengakses variabel yang diminta. Pembatasan ini, meskipun secara semantik membatasi, sangat menyederhanakan runtime, dan memfasilitasi validasi bytecode; penyederhanaan seperti itu mengurangi overhead sumber daya Jawa pada saat pasar peka terhadap masalah-masalah seperti itu, dan dengan demikian membantunya mendapatkan daya tarik di pasar. Mereka juga berarti bahwa tidak perlu token yang setara dengan yang
.
digunakan dalam C atau C ++. Meskipun Java bisa digunakan->
dengan cara yang sama seperti C dan C ++, pembuatnya memilih untuk menggunakan karakter tunggal.
karena itu tidak diperlukan untuk tujuan lain.Dalam C # dan bahasa .NET lainnya, variabel dapat mengidentifikasi objek atau memegang tipe data komposit secara langsung. Ketika digunakan pada variabel tipe data komposit,
.
bertindak atas isi variabel; ketika digunakan pada variabel tipe referensi,.
bertindak atas objek yang diidentifikasioleh itu. Untuk beberapa jenis operasi, perbedaan semantik tidak terlalu penting, tetapi untuk yang lain itu. Situasi paling problematis adalah situasi di mana metode tipe data komposit yang akan memodifikasi variabel yang digunakan, dipanggil pada variabel read-only. Jika suatu upaya dilakukan untuk memanggil metode pada nilai atau variabel read-only, kompiler umumnya akan menyalin variabel, membiarkan metode bertindak atas hal itu, dan membuang variabel. Ini umumnya aman dengan metode yang hanya membaca variabel, tetapi tidak aman dengan metode yang menulisnya. Sayangnya,. Belum memiliki sarana untuk menunjukkan metode mana yang dapat digunakan dengan aman dengan substitusi tersebut dan yang tidak.Di Swift, metode pada agregat dapat secara tegas menunjukkan apakah mereka akan memodifikasi variabel yang mereka gunakan, dan kompiler akan melarang penggunaan metode bermutasi pada variabel read-only (daripada meminta mereka memalsukan salinan sementara dari variabel yang kemudian akan dibuang). Karena perbedaan ini, menggunakan
.
token untuk memanggil metode yang memodifikasi variabel yang mereka gunakan jauh lebih aman di Swift daripada di .NET. Sayangnya, fakta bahwa.
token yang sama digunakan untuk tujuan itu untuk bertindak pada objek eksternal yang diidentifikasi oleh variabel berarti kemungkinan untuk kebingungan tetap ada.Jika punya mesin waktu dan kembali ke penciptaan C # dan / atau Swift, salah satu surut bisa menghindari banyak kebingungan seputar isu-isu tersebut dengan memiliki bahasa menggunakan
.
dan->
token dalam mode lebih dekat dengan C ++ penggunaan. Metode agregat dan tipe referensi dapat digunakan.
untuk bertindak berdasarkan variabel yang mereka gunakan , dan->
untuk bertindak berdasarkan nilai (untuk komposit) atau hal yang diidentifikasi dengan demikian (untuk jenis referensi). Namun, tidak ada bahasa yang dirancang seperti itu.Dalam C #, praktik normal untuk metode untuk memodifikasi variabel yang dipanggil adalah untuk lulus variabel sebagai
ref
parameter ke metode. Dengan demikian panggilanArray.Resize(ref someArray, 23);
saatsomeArray
mengidentifikasi array 20 elemen akan menyebabkansomeArray
mengidentifikasi array baru dari 23 elemen, tanpa mempengaruhi array asli. Penggunaanref
memperjelas bahwa metode harus diharapkan untuk memodifikasi variabel yang diminta. Dalam banyak kasus, menguntungkan untuk dapat memodifikasi variabel tanpa harus menggunakan metode statis; Alamat swift artinya dengan menggunakan.
sintaks. Kerugiannya adalah tidak ada kejelasan tentang metode apa yang berlaku pada variabel dan metode apa yang berlaku pada nilai.sumber
Bagi saya ini lebih masuk akal jika Anda pertama kali mengganti konstanta Anda dengan variabel:
Baris pertama tidak perlu mengubah ukuran
a
. Secara khusus, tidak perlu melakukan alokasi memori apa pun. Terlepas dari nilaii
, ini adalah operasi yang ringan. Jika Anda membayangkan bahwa di bawah kapa
adalah sebuah pointer, itu bisa menjadi pointer konstan.Baris kedua mungkin jauh lebih rumit. Tergantung pada nilai
i
danj
, Anda mungkin perlu melakukan manajemen memori. Jika Anda membayangkan itue
adalah pointer yang menunjuk ke isi array, Anda tidak bisa lagi berasumsi bahwa itu adalah pointer konstan; Anda mungkin perlu mengalokasikan blok memori baru, menyalin data dari blok memori lama ke blok memori baru, dan mengubah pointer.Tampaknya para perancang bahasa telah berusaha menjaga (1) seringan mungkin. Karena (2) mungkin melibatkan penyalinan, mereka telah menggunakan solusi yang selalu bertindak seolah-olah Anda melakukan penyalinan.
Ini rumit, tetapi saya senang mereka tidak membuatnya lebih rumit dengan misalnya kasus khusus seperti "jika dalam (2) i dan j adalah konstanta waktu kompilasi dan kompiler dapat menyimpulkan bahwa ukuran e tidak akan untuk mengubah, maka kita tidak menyalin " .
Akhirnya, berdasarkan pemahaman saya tentang prinsip-prinsip desain bahasa Swift, saya pikir aturan umum adalah ini:
let
) selalu di mana-mana secara default, dan tidak akan ada kejutan besar.var
) hanya jika itu benar-benar diperlukan, dan sangat bervariasi dalam kasus-kasus tersebut, karena akan ada kejutan [di sini: salinan implisit aneh array dalam beberapa tetapi tidak semua situasi].sumber
Apa yang saya temukan adalah: Array akan menjadi salinan yang bisa diubah dari yang direferensikan jika dan hanya jika operasi memiliki potensi untuk mengubah panjang array . Dalam contoh terakhir Anda,
f[0..2]
pengindeksan dengan banyak, operasi memiliki potensi untuk mengubah panjangnya (mungkin duplikat tidak diizinkan), sehingga disalin.sumber
var
array sekarang sepenuhnya bisa berubah danlet
array benar-benar tidak dapat diubah.String dan array Delphi memiliki "fitur" yang sama persis. Ketika Anda melihat implementasinya, itu masuk akal.
Setiap variabel adalah penunjuk ke memori dinamis. Memori itu berisi jumlah referensi yang diikuti oleh data dalam array. Jadi Anda dapat dengan mudah mengubah nilai dalam array tanpa menyalin seluruh array atau mengubah pointer apa pun. Jika Anda ingin mengubah ukuran array, Anda harus mengalokasikan lebih banyak memori. Dalam hal ini, variabel saat ini akan menunjuk ke memori yang baru dialokasikan. Tetapi Anda tidak dapat dengan mudah melacak semua variabel lain yang menunjuk ke array asli, jadi biarkan saja.
Tentu saja, tidak akan sulit untuk membuat implementasi yang lebih konsisten. Jika Anda ingin semua variabel melihat ukurannya, lakukan ini: Setiap variabel adalah penunjuk ke wadah yang disimpan dalam memori dinamis. Wadah menampung tepat dua hal, penghitungan referensi dan penunjuk ke data array aktual. Data array disimpan dalam blok memori dinamis yang terpisah. Sekarang hanya ada satu pointer ke data array, sehingga Anda dapat dengan mudah mengubah ukurannya, dan semua variabel akan melihat perubahannya.
sumber
Banyak pengguna awal Swift mengeluh tentang semantik array rawan kesalahan ini dan Chris Lattner telah menulis bahwa semantik array telah direvisi untuk memberikan semantik nilai penuh ( tautan Pengembang Apple untuk mereka yang memiliki akun ). Kami harus menunggu setidaknya untuk beta berikutnya untuk melihat apa artinya ini sebenarnya.
sumber
Saya menggunakan .copy () untuk ini.
sumber
Apakah ada perubahan perilaku array di versi Swift yang lebih baru? Saya baru saja menjalankan contoh Anda:
Dan hasil saya adalah [1, 42, 3] dan [1, 2, 3]
sumber