Masalah pemrograman apa yang paling baik diselesaikan dengan menggunakan pointer? [Tutup]


58

Yah, pada dasarnya saya mengerti cara menggunakan pointer, tetapi bukan cara terbaik untuk menggunakannya untuk melakukan pemrograman yang lebih baik.

Apa proyek atau masalah yang baik untuk diselesaikan yang melibatkan penggunaan pointer sehingga saya bisa memahaminya dengan lebih baik?


Tanyakan tentang masalah pemrograman apa yang paling baik diselesaikan dengan menggunakan pointer, bukan proyek / program apa. Anda dapat menulis sebuah program baik menggunakan pointer atau tidak - mereka (proyek) hanya sebuah konstruksi yang terlalu besar untuk jawaban yang bermakna.
Oded

37
Masalah pemrograman dibuat menggunakan pointer, tidak diselesaikan. :)
xpda

4
Sebenarnya, saya bertanya-tanya masalah mana yang bisa diselesaikan tanpa pointer. Sangat sedikit pasti.
deadalnix

4
@deadalnix, itu tergantung apakah yang Anda maksud adalah pointer eksplisit atau pointer implisit. Jika Anda maksud pointer implisit (yaitu, ada pointer yang digunakan di suatu tempat), maka tidak mungkin untuk menulis program apa pun yang menggunakan stack atau heap. Jika maksud Anda adalah pointer eksplisit maka Anda tidak dibatasi, Anda dapat melakukan ukuran alokasi apa pun dengan membuat bingkai stack baru (pada dasarnya menggunakan panggilan fungsi) dan membatalkan alokasi menggunakan panggilan ekor, dengan asumsi mereka dioptimalkan jauh oleh kompiler.
dan_waterworth

3
huh, beberapa jawaban ini mengerikan.

Jawaban:


68

Memanipulasi sejumlah besar data dalam memori adalah tempat pointer benar-benar bersinar.

Melewati objek besar dengan referensi setara dengan hanya meneruskan angka lama yang polos. Anda dapat memanipulasi bagian-bagian yang diperlukan secara langsung sebagai lawan menyalin objek, mengubahnya, lalu mengirimkan kembali salinan untuk ditempatkan di tempat asli.


12
Ini adalah jawaban yang bagus dan saya akan menambahkan bahwa Anda tidak perlu objek besar untuk memanipulasi data dalam jumlah besar; kadang-kadang memanipulasi objek besar akan melakukannya, tetapi masalah yang lebih umum adalah memanipulasi banyak objek kecil dengan sangat sering. Yang mana, jika Anda pikirkan, adalah hampir semua program komputer.
jhocking

44

Konsep pointer memungkinkan Anda untuk merujuk ke data berdasarkan alamat tanpa menduplikasi penyimpanan data. Pendekatan ini memungkinkan penulisan algoritma yang efisien seperti:

  1. Pengurutan
    Saat memindahkan data dalam algoritma pengurutan, Anda dapat memindahkan penunjuk alih-alih data itu sendiri — pikirkan untuk mengurutkan jutaan baris pada string 100 karakter; Anda menyimpan banyak gerakan data yang tidak perlu.

  2. Daftar tertaut
    Anda dapat menyimpan lokasi item berikutnya dan / atau sebelumnya dan bukan seluruh data yang terkait dengan catatan.

  3. Melewati parameter
    Dalam hal ini, Anda meneruskan alamat data bukan data itu sendiri. Sekali lagi, pikirkan algoritma kompresi nama yang berjalan di jutaan baris.

Konsep ini dapat diperluas ke struktur data seperti database relasional di mana pointer mirip dengan kunci asing . Beberapa bahasa tidak menganjurkan penggunaan pointer seperti C # dan COBOL.

Contohnya dapat ditemukan di banyak tempat seperti:

Posting berikut mungkin relevan dalam beberapa cara:


14
Saya tidak akan mengatakan C # tidak mendorong penggunaan pointer; sebaliknya, itu tidak mendorong penggunaan pointer yang tidak dikelola - ada banyak hal dalam C # yang akan diedarkan dengan referensi alih-alih nilai.
Blakomen

Bagus, penjelasan bagus
Sabby

5
Harus downvote untuk komentar C #. "Referensi" C # adalah sepadan dengan pointer, hanya saja C # membuat Anda jauh dari mengetahui bahwa sebenarnya Anda hanya berurusan dengan pointer ke objek di tumpukan yang dikelola.
MattDavey

1
@MattDavey: referensi C # bukan petunjuk. Anda tidak dapat menambahkan dari mereka, menggunakannya untuk mengindeks array, menggunakan banyak tipuan, dll. Anda tidak dapat memiliki referensi ke referensi, misalnya.
Billy ONeal

5
@Billy ONeal gagasan bahwa "itu hanya pointer jika Anda dapat melakukan aritmatika di atasnya" terus terang menggelikan, tapi saya tidak punya kesabaran untuk berdebat dengan Anda.
MattDavey

29

Saya terkejut tidak ada jawaban lain yang menyebutkan ini: pointer memungkinkan Anda untuk membuat struktur data yang tidak bersebelahan dan non-linear, di mana suatu elemen mungkin terkait dengan beberapa lainnya dengan cara yang kompleks.

Daftar yang ditautkan (tunggal, ganda, dan ditautkan melingkar), pohon (merah-hitam, AVL, trie, biner, partisi ruang ...), dan grafik adalah semua contoh struktur yang dapat dibangun paling alami dalam hal referensi daripada hanya nilai .


Anda lupa daftar tertaut XOR [=
dan_waterworth

@dan_waterworth: Tidak. Saya tahu sihir hitam itu terlalu baik.
Jon Purdy

8

Salah satu cara sederhana adalah dalam Polimorfisme. Polimorfisme hanya berfungsi dengan pointer.

Juga, Anda menggunakan pointer kapan saja Anda membutuhkan alokasi memori dinamis. Dalam C, ini biasanya terjadi ketika Anda perlu menyimpan data ke dalam array tetapi Anda tidak tahu ukurannya pada waktu kompilasi. Anda kemudian akan memanggil malloc untuk mengalokasikan memori dan pointer untuk mengaksesnya. Juga, apakah Anda mengetahuinya atau tidak, ketika Anda menggunakan array Anda menggunakan pointer.

for(int i = 0; i < size; i++)
   std::cout << array[i];

adalah setara dengan

for(int i = 0; i < size; i++)
   std::cout << *(array + i);

Pengetahuan ini memungkinkan Anda untuk melakukan hal-hal yang sangat keren seperti menyalin seluruh array dalam satu baris:

while( (*array1++ = *array2++) != '\0')

Di c ++, Anda menggunakan yang baru untuk mengalokasikan memori untuk objek dan menyimpannya ke dalam pointer. Anda melakukan ini kapan saja Anda perlu membuat objek selama run-time alih-alih selama waktu kompilasi (yaitu metode membuat objek baru dan menyimpannya ke dalam daftar).

Untuk memahami pointer lebih baik:

  1. Temukan beberapa proyek yang bermain-main dengan c-string dan array tradisional.
  2. Temukan beberapa proyek yang menggunakan warisan.

  3. Inilah proyek tempat saya memotong gigi:

Baca dalam dua matriks nxn dari file dan lakukan operasi ruang vektor dasar pada mereka dan cetak hasilnya ke layar.

Untuk melakukan ini, Anda harus menggunakan array dinamis dan merujuk array Anda dengan pointer karena Anda akan memiliki dua array array (array dinamis multi-dimensi). Setelah Anda menyelesaikan proyek itu, Anda akan memiliki ide yang cukup bagus bagaimana menggunakan pointer.


2
"Polimorfisme hanya berfungsi dengan pointer." Tidak akurat: polimorfisme juga berfungsi dengan referensi. Yang pada akhirnya adalah petunjuk, tetapi saya pikir perbedaan itu penting, terutama bagi para pemula.
Laurent Pireyn

contoh Anda untuk menyalin array dalam satu baris benar-benar untuk menyalin string yang diakhiri nol dalam satu baris, bukan array umum apa pun.
tcrosley

8

Untuk benar-benar memahami mengapa pointer penting, Anda perlu memahami perbedaan antara alokasi tumpukan dan alokasi tumpukan.

Berikut ini adalah contoh alokasi tumpukan:

struct Foo {
  int bar, baz
};

void foo(void) {
  struct Foo f;
}

Objek yang dialokasikan pada stack hanya ada selama durasi eksekusi fungsi saat ini. Ketika panggilan untuk fookeluar dari ruang lingkup begitu juga variabel f.

Satu kasus di mana ini menjadi masalah adalah ketika Anda perlu mengembalikan sesuatu selain tipe integral dari suatu fungsi (misalnya struktur Foo dari contoh di atas).

Misalnya, fungsi berikut akan menghasilkan apa yang disebut "perilaku tidak terdefinisi."

struct Foo {
  int bar, baz
};

struct Foo *foo(void) {
  struct Foo f;
  return &f;
}

Jika Anda ingin mengembalikan sesuatu seperti struct Foo *dari fungsi yang benar-benar Anda butuhkan adalah alokasi tumpukan:

struct Foo {
  int bar, baz
};

struct Foo *foo(void) {
  return malloc(sizeof(struct Foo));
}

Fungsi mallocmengalokasikan objek pada heap dan mengembalikan pointer ke objek itu. Perhatikan bahwa istilah "objek" digunakan secara longgar di sini, yang berarti "sesuatu" daripada objek dalam arti pemrograman berorientasi objek.

Masa objek yang dialokasikan heap dikendalikan oleh programmer. Memori untuk objek ini akan dicadangkan sampai programmer membebaskannya, yaitu dengan menelepon free()atau sampai program keluar.

Sunting : Saya gagal melihat pertanyaan ini ditandai sebagai pertanyaan C ++. Operator C ++ newdan new[]melakukan fungsi yang sama dengan malloc. Operator deletedan delete[]analog dengan free. Sementara newdan deleteharus digunakan secara eksklusif untuk mengalokasikan dan membebaskan objek C ++, penggunaan mallocdan freesepenuhnya legal dalam kode C ++.


1
(+1) juga dikenal sebagai "variabel yang dialokasikan statis" dan "variabel yang dialokasikan dinamis" ;-)
umlcat

4

Tuliskan proyek non-sepele dalam C dan Anda AKAN harus memikirkan bagaimana / kapan menggunakan pointer. Dalam C ++ Anda sebagian besar akan menggunakan objek yang diaktifkan RAII yang mengelola pointer secara internal, tetapi dalam C raw pointer memiliki peran yang jauh lebih umum. Adapun jenis proyek apa yang harus Anda lakukan, bisa apa saja non-sepele:

  • Server web kecil dan sederhana
  • Unix alat baris perintah (less, cat, sort dll.)
  • Beberapa proyek aktual yang ingin Anda lakukan sendiri dan bukan hanya untuk belajar

Saya merekomendasikan yang terakhir.


Jadi saya hanya pergi untuk proyek yang ingin saya lakukan (Seperti game 2D) dan seharusnya saya perlu dan belajar petunjuk?
dysoco

3
Dalam pengalaman saya, melakukan proyek nyata yang sedikit di luar jangkauan keterampilan Anda adalah cara terbaik untuk belajar. Konsep dapat dipelajari dengan membaca dan menulis program "mainan" kecil. Namun, untuk benar-benar belajar bagaimana Anda dapat menggunakan konsep-konsep itu untuk menulis program yang lebih baik, Anda hanya perlu menulis program non-mainan.
Viktor Dahl

3

Hampir semua masalah pemrograman yang dapat diselesaikan dengan pointer dapat diselesaikan dengan jenis referensi yang lebih aman lainnya (tidak mengacu pada referensi C ++ , tetapi konsep CS umum memiliki variabel merujuk pada nilai data yang disimpan di tempat lain).

Pointer dengan menjadi implementasi referensi tingkat rendah yang spesifik, di mana Anda dapat secara langsung memanipulasi alamat memori sangat kuat, tetapi bisa sedikit berbahaya untuk digunakan (misalnya, arahkan ke lokasi memori di luar program).

Manfaat menggunakan pointer secara langsung adalah mereka akan sedikit lebih cepat dengan tidak harus melakukan pemeriksaan keamanan. Bahasa seperti Java yang tidak secara langsung mengimplementasikan pointer C-style akan mengalami sedikit peningkatan kinerja, tetapi akan mengurangi banyak jenis situasi sulit untuk di-debug.

Adapun mengapa Anda perlu tipuan, daftar ini cukup panjang, tetapi pada dasarnya dua gagasan utama adalah:

  1. Menyalin nilai objek besar lambat dan akan membuat objek disimpan dalam RAM dua kali (berpotensi sangat mahal), tetapi menyalin dengan referensi hampir seketika menggunakan hanya beberapa byte RAM (untuk alamat). Misalnya, Anda memiliki ~ 1000 objek besar (masing-masing berukuran sekitar 1MB RAM) dalam memori, dan pengguna Anda harus dapat memilih objek saat ini (yang akan ditindaklanjuti oleh pengguna). Memiliki variabel selected_objectyang merupakan referensi ke salah satu objek jauh lebih efisien daripada menyalin nilai objek saat ini ke variabel baru.
  2. Memiliki struktur data yang rumit yang merujuk ke objek lain seperti daftar atau pohon yang ditautkan, di mana setiap item dalam struktur data merujuk ke item lain dalam struktur data. Manfaat merujuk ke item lain dalam struktur data berarti Anda tidak harus memindahkan setiap item dalam daftar di memori hanya karena Anda memasukkan item baru ke tengah daftar (dapat memiliki sisipan waktu yang konstan).

2

Manipulasi gambar tingkat piksel hampir selalu lebih mudah dan lebih cepat menggunakan pointer. Terkadang hanya dimungkinkan menggunakan pointer.


1
Saya tidak berpikir pernyataan "kadang-kadang hanya mungkin menggunakan pointer" benar. Ada bahasa yang tidak mengekspos pointer ke pengembang, namun mereka dapat digunakan untuk memecahkan masalah di ruang yang sama.
Thomas Owens

Mungkin ... Saya tidak memiliki pengetahuan tentang semua bahasa yang tersedia, tetapi saya tentu tidak ingin menulis rutin untuk menerapkan filter konvolusi ke gambar menggunakan bahasa yang tidak memiliki pointer.
Dave Nay

@ Thomas, sementara Anda benar, pertanyaannya adalah tentang c ++ yang mengekspos pointer ke pengembang.
Jonathan Henson

@ Jonathan Meski begitu, jika Anda dapat memecahkan masalah dalam bahasa yang tidak mengekspos pointer, maka itu juga mungkin untuk memecahkan masalah itu dalam bahasa yang mengekspos pointer tanpa menggunakan pointer. Itu membuat kalimat pertama benar, tetapi kalimat kedua salah - tidak ada masalah yang bisa diselesaikan tanpa menggunakan petunjuk.
Thomas Owens

1
Sebaliknya, pemrosesan gambar sering melibatkan "koordinat aritmatika" - katakanlah, mengambil sinus dan kosinus sudut dan mengalikannya dengan koordinat x dan y. Dalam kasus lain diperlukan replikasi piksel wrap-around atau out-of-border. Dalam hal ini, aritmatika pointer agak tidak memadai.
rwong

1

Pointer digunakan dalam begitu banyak bahasa pemrograman di bawah permukaan tanpa mengganggu pengguna tentang hal itu. C / C ++ hanya memberi Anda akses ke mereka.

Kapan menggunakannya: Sesering mungkin, karena menyalin data tidak efisien. Kapan tidak menggunakannya: Bila Anda ingin dua salinan yang dapat diubah secara individual. (Apa yang pada dasarnya akan menyalin konten object_1 ke tempat lain di memori dan mengembalikan pointer - kali ini menunjuk ke object_2)


1

Pointer adalah bagian penting untuk setiap implementasi struktur data dalam C dan struktur data adalah bagian penting dari setiap program non-sepele.

Jika Anda ingin mengetahui mengapa pointer itu sangat vital, maka saya sarankan untuk mempelajari apa itu daftar yang ditautkan dan mencoba untuk menulisnya tanpa menggunakan pointer. Saya belum membuat Anda tantangan yang mustahil, (PETUNJUK: pointer digunakan untuk referensi lokasi dalam memori, bagaimana Anda mereferensikan hal-hal dalam array?).


+1 untuk menyebutkan struktur data, penggunaan dalam algoritma adalah konsekuensi alami.
Matthieu M.

0

Sebagai contoh kehidupan nyata, membangun buku pesanan terbatas.

umpan ITCH 4.1, misalnya, memiliki konsep pesanan "ganti", di mana harga (dan karenanya, prioritas) dapat berubah. Anda ingin dapat mengambil pesanan dan memindahkannya ke tempat lain. Menerapkan antrian ujung ganda dengan pointer membuat operasi sangat mudah.


0

Membaca dengan teliti berbagai situs StackExchange, saya menyadari bahwa itu agak mode untuk mengajukan pertanyaan seperti ini. Dengan risiko kritik dan downvotes, saya akan jujur. Ini tidak dimaksudkan untuk menyulut atau menyulut api, saya hanya bermaksud membantu, dengan memberikan penilaian yang jujur ​​atas pertanyaan itu.

Dan penilaian itu adalah sebagai berikut: Ini adalah pertanyaan yang sangat aneh untuk ditanyakan pada seorang programmer C. Hampir semua yang dikatakannya adalah "Saya tidak tahu C." Jika saya menganalisis sedikit lebih jauh dan lebih sinis, ada nada tindak lanjut dari "pertanyaan" ini: "Apakah ada beberapa jalan pintas yang dapat saya ambil untuk secara cepat dan tiba-tiba memperoleh pengetahuan seorang programmer C yang berpengalaman, tanpa mengabdikan waktu apa pun untuk belajar mandiri? " Setiap "jawaban" yang dapat diberikan seseorang bukanlah pengganti untuk pergi dan melakukan kerja keras untuk memahami konsep yang mendasarinya dan penggunaannya.

Saya merasa lebih konstruktif untuk belajar C dengan baik, langsung, daripada pergi di web dan bertanya kepada orang-orang ini. Ketika Anda tahu C dengan baik Anda tidak akan repot-repot mengajukan pertanyaan seperti ini, itu akan seperti bertanya "masalah apa yang paling baik diselesaikan dengan menggunakan sikat gigi?"


0

Meskipun pointer benar-benar bersinar saat bekerja dengan objek memori besar, masih ada cara untuk melakukan hal yang sama tanpa mereka.

Pointer sangat penting untuk apa yang disebut pemrograman dinamis , itu adalah ketika Anda tidak tahu berapa banyak memori yang Anda perlukan sebelum program Anda dijalankan. Dalam pemrograman dinamis, Anda dapat meminta potongan memori selama runtime dan menempatkan data Anda sendiri ke dalamnya - sehingga Anda perlu pointer atau referensi (perbedaannya tidak penting di sini) untuk dapat bekerja dengan potongan data tersebut.

Selama Anda dapat mengklaim memori tertentu selama runtime dan menempatkan data Anda ke dalam memori yang baru diperoleh, Anda dapat melakukan hal berikut:

  1. Anda dapat memiliki struktur data yang diperluas sendiri. Itu adalah struktur yang dapat memperluas diri dengan mengklaim memori tambahan selama kapasitasnya habis. Properti utama dari setiap struktur self-extending adalah bahwa ia terdiri dari blok memori kecil (disebut node, item daftar dll tergantung pada struktur) dan setiap blok berisi referensi ke blok lain (s). Struktur "tertaut" ini membangun sebagian besar tipe data modern: grafik, pohon, daftar, dll.

  2. Anda dapat memprogram menggunakan paradigma OOP (Object-Oriented Programming). Keseluruhan OOP didasarkan pada penggunaan variabel tidak langsung, tetapi referensi ke instance kelas (objek disebut) dan memanipulasi mereka. Tidak ada satu pun instance yang dapat ada tanpa pointer (meskipun dimungkinkan untuk menggunakan kelas statis saja bahkan tanpa pointer, itu agak pengecualian).


0

Lucu, saya baru saja menjawab pertanyaan tentang C ++ dan berbicara tentang petunjuk.

Versi singkatnya adalah Anda TIDAK PERNAH membutuhkan pointer kecuali 1) perpustakaan yang Anda gunakan memaksa Anda 2) Anda perlu referensi yang dapat dibatalkan.

Jika Anda membutuhkan sebuah array, daftar, string dll hanya memilikinya di stack dan gunakan objek stl. Mengembalikan atau melewati objek stl adalah cepat (fakta tidak dicentang) karena mereka memiliki kode internal yang menyalin pointer alih-alih objek dan hanya akan menyalin data jika Anda menulisnya. Ini adalah C ++ reguler bahkan C ++ 11 baru yang akan membuatnya lebih mudah pada penulis perpustakaan.

Pertanyaan Anda mungkin dijawab di bagian ini

Jika Anda menggunakan pointer pastikan itu dalam salah satu dari dua kondisi ini. 1) Anda memberikan masukan yang mungkin dapat dibatalkan. Contohnya adalah nama file opsional. 2) Jika Anda ingin memberikan kepemilikan. Seperti jika Anda melewatkan atau mengembalikan pointer, Anda tidak memiliki salinan apa pun yang tersisa atau menggunakan pointer yang Anda berikan

ptr=blah; func(ptr); //never use ptr again for here on out.

Tetapi saya belum pernah menggunakan pointer atau pointer pintar untuk waktu yang sangat lama dan saya telah membuat profil aplikasi saya. Ini berjalan sangat cepat.

CATATAN TAMBAHAN: Saya perhatikan bahwa saya menulis struct saya sendiri dan saya menyebarkannya. Jadi bagaimana saya melakukan ini tanpa menggunakan pointer? Ini bukan wadah STL jadi lewat ref lambat. Saya selalu memuat daftar data saya / deques / peta dan semacamnya. Saya tidak ingat mengembalikan benda apa pun kecuali itu semacam daftar / peta. Bahkan string. Saya melihat kode untuk objek tunggal dan saya perhatikan saya melakukan sesuatu seperti ini { MyStruct v; func(v, someinput); ... } void func(MyStruct&v, const D&someinput) { fillV; }jadi saya cukup banyak mengembalikan objek (banyak) atau melakukan preallocate / pass dalam referensi untuk mengisi (tunggal).

Sekarang jika Anda menulis deque Anda sendiri, peta, dll Anda harus menggunakan pointer. Tetapi Anda tidak perlu melakukannya. Biarkan STL dan mungkin meningkatkan kekhawatiran tentang itu. Anda hanya perlu menulis data dan solusinya. Bukan wadah untuk menampungnya;)

Saya harap Anda sekarang tidak pernah menggunakan pointer: D. Semoga berhasil berurusan dengan lib yang memaksa Anda melakukannya


0

Pointer sangat berguna untuk bekerja dengan perangkat yang dipetakan memori. Anda dapat menentukan struktur yang mencerminkan (katakanlah) register kontrol, kemudian menetapkannya ke alamat register kontrol aktual dalam memori dan memanipulasi secara langsung. Anda juga dapat mengarahkan langsung ke buffer transfer pada kartu atau chip jika MMU telah memetakannya ke dalam ruang memori sistem.


0

Saya melihat pointer sebagai jari telunjuk saya, kami gunakan untuk melakukan beberapa hal:

  1. ketika seseorang meminta Anda untuk sesuatu yang tidak dapat Anda bawa, seperti di mana jalan ini dan itu, saya akan "menunjuk" di jalan ini, dan itulah yang kami lakukan jika ada argumen dengan referensi
  2. ketika menghitung atau melintasi sesuatu, kita akan menggunakan jari yang sama, dan itulah yang kita lakukan dalam array

tolong maafkan jawaban yang buruk ini


0

Karena pertanyaan Anda ditandai C ++, saya akan menjawab pertanyaan Anda untuk bahasa itu.

Dalam C ++ ada perbedaan antara pointer dan referensi, oleh karena itu ada dua skenario di mana pointer (atau smart pointer) diperlukan untuk memfasilitasi perilaku tertentu. Mereka dapat digunakan dalam keadaan lain, namun Anda bertanya bagaimana "terbaik untuk menggunakannya", dan dalam semua keadaan lain ada alternatif yang lebih baik.

1. Polimorfisme

Pointer kelas dasar memungkinkan Anda untuk memanggil metode virtual yang tergantung pada jenis objek yang ditunjuk oleh pointer.

2. Membuat objek gigih

Pointer diperlukan saat membuat objek secara dinamis (di heap bukan stack). Ini diperlukan ketika Anda ingin agar objek seumur hidup lebih panjang dari ruang lingkup di mana ia dibuat.

Dalam hal "proyek bagus atau masalah untuk diselesaikan", seperti yang sudah dikatakan orang lain di sini, proyek non-sepele akan menggunakan pointer.


Mungkin seseorang lebih suka mengiris objek daripada menggunakan pointer. Selamat debugging: D
deadalnix
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.