Mengapa kita harus menyebutkan tipe data dari variabel dalam C

19

Biasanya dalam C, kita harus memberi tahu komputer tipe data dalam deklarasi variabel. Misalnya dalam program berikut, saya ingin mencetak jumlah dua angka floating point X dan Y.

#include<stdio.h>
main()
{
  float X=5.2;
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}

Saya harus memberi tahu kompiler jenis variabel X.

  • Tidak bisakah kompiler menentukan jenisnya Xsendiri?

Ya, bisa jika saya melakukan ini:

#define X 5.2

Sekarang saya dapat menulis program saya tanpa memberi tahu kompiler jenis Xsebagai:

#include<stdio.h>
#define X 5.2
main()
{
  float Y=5.1;
  float Z;
  Z=Y+X;
  printf("%f",Z);

}  

Jadi kita melihat bahwa bahasa C memiliki beberapa fitur, yang dapat menentukan jenis data sendiri. Dalam kasus saya ditentukan bahwa itu Xadalah tipe float.

  • Mengapa kita harus menyebutkan tipe data, ketika kita mendeklarasikan sesuatu di main ()? Mengapa kompiler tidak dapat menentukan tipe data dari variabelnya sendiri main()seperti halnya di #define.
pengguna106313
sumber
14
Sebenarnya kedua program tersebut tidak setara, karena mereka dapat memberikan output yang sedikit berbeda! 5.2adalah double, jadi program pertama membulatkan literal ganda menjadi floatpresisi, kemudian menambahkannya sebagai float, sedangkan putaran kedua representasi ganda 5,1 kembali ke doubledan menambahkannya ke doublenilai 5,2 menggunakan doubletambahan, kemudian putaran hasil perhitungan perhitungan ke floatpresisi . Karena pembulatan terjadi di tempat yang berbeda, hasilnya dapat berubah. Ini hanya satu contoh untuk jenis variabel yang mempengaruhi perilaku program yang identik.
12
Ketika Anda melakukannya #define X 5.2, Xitu bukan variabel, tetapi sebuah konstanta, jadi ia secara harfiah diganti menjadi preprosesor dengan 5.2tempat yang Anda sebutkan X. Anda tidak dapat menetapkan kembali X.
scriptin
16
Sama seperti catatan: ini adalah berkah dan kutukan. Di satu sisi, Anda harus mengetik beberapa karakter ketika kompiler benar-benar bisa melakukannya untuk Anda (C ++ autosebenarnya melakukan apa yang Anda inginkan). Di sisi lain, jika Anda pikir Anda tahu apa kode Anda lakukan, dan Anda benar-benar mengetik sesuatu yang lain, mengetik statis seperti ini akan menangkap kesalahan sebelumnya, sebelum menjadi masalah besar. Setiap bahasa menemukan keseimbangan: pengetikan statis, pengetikan tipe, pengetikan dinamis. Untuk beberapa tugas, pengetikan ekstra sebenarnya sepadan. Bagi yang lain, ini sia-sia.
Cort Ammon - Reinstate Monica
Pelajari Ocaml dan / atau Haskell .... Anda akan senang dengan kemampuan inferensi tipenya.
Basile Starynkevitch

Jawaban:

46

Anda membandingkan deklarasi variabel dengan #defines, yang salah. Dengan #define, Anda membuat pemetaan antara pengidentifikasi dan cuplikan kode sumber. Preprocessor C kemudian akan secara harfiah mengganti setiap kejadian dari pengidentifikasi itu dengan potongan yang disediakan. Penulisan

#define FOO 40 + 2
int foos = FOO + FOO * FOO;

akhirnya menjadi hal yang sama dengan kompiler seperti menulis

int foos = 40 + 2 + 40 + 2 * 40 + 2;

Anggap saja sebagai salin & tempel otomatis.

Juga, variabel normal dapat dipindahkan, sementara makro dibuat dengan #definetidak bisa (meskipun Anda bisa menggantinya #define). Ekspresi FOO = 7akan menjadi kesalahan kompiler, karena kami tidak dapat menetapkan "rvalues": 40 + 2 = 7ilegal.

Jadi, mengapa kita perlu tipe sama sekali? Beberapa bahasa tampaknya menghilangkan jenis, ini sangat umum dalam bahasa scripting. Namun, mereka biasanya memiliki sesuatu yang disebut "pengetikan dinamis" di mana variabel tidak memiliki tipe tetap, tetapi nilainya. Meskipun ini jauh lebih fleksibel, itu juga kurang berkinerja. C menyukai kinerja, sehingga memiliki konsep variabel yang sangat sederhana dan efisien:

Ada hamparan memori yang disebut "tumpukan". Setiap variabel lokal sesuai dengan area pada stack. Sekarang pertanyaannya adalah berapa lama byte ini harus? Di C, masing-masing jenis memiliki ukuran yang terdefinisi dengan baik yang dapat Anda query sizeof(type). Kompiler perlu mengetahui tipe setiap variabel sehingga dapat memesan jumlah ruang yang benar pada stack.

Mengapa konstanta tidak dibuat dengan #definememerlukan anotasi jenis? Mereka tidak disimpan di tumpukan. Alih-alih, #definemembuat cuplikan kode sumber yang dapat digunakan kembali dengan cara yang sedikit lebih bisa dipelihara daripada menyalin & menempel. Literal dalam kode sumber seperti "foo"atau 42.87disimpan oleh kompiler baik sebaris sebagai instruksi khusus, atau dalam bagian data terpisah dari biner yang dihasilkan.

Namun, literal memang memiliki tipe. String literal adalah a char *. 42adalah inttetapi dapat juga digunakan untuk tipe yang lebih pendek (penyempitan konversi). 42.8akan menjadi double. Jika Anda memiliki literal dan ingin memiliki jenis yang berbeda (misalnya untuk membuat 42.8sebuah float, atau 42sebuah unsigned long int), maka Anda dapat menggunakan akhiran - surat setelah literal bahwa perubahan bagaimana compiler memperlakukan yang literal. Dalam kasus kami, kami dapat mengatakan 42.8fatau 42ul.

Beberapa bahasa memiliki pengetikan statis seperti dalam C, tetapi anotasi jenis bersifat opsional. Contohnya adalah ML, Haskell, Scala, C #, C ++ 11, dan Go. Bagaimana cara kerjanya? Sihir? Tidak, ini disebut "inferensi tipe". Di C # dan Go, kompiler melihat sisi kanan tugas, dan menyimpulkan jenis itu. Ini cukup mudah jika sisi kanan adalah literal seperti 42ul. Maka jelaslah apa jenis variabel seharusnya. Bahasa lain juga memiliki algoritma yang lebih kompleks yang memperhitungkan bagaimana suatu variabel digunakan. Misalnya, jika Anda melakukannya x/2, maka xtidak dapat berupa string tetapi harus memiliki beberapa jenis numerik.

amon
sumber
Terima kasih telah menjelaskan. Hal yang saya pahami adalah ketika kita mendeklarasikan tipe variabel (lokal atau global), kita sebenarnya memberitahu kompiler, berapa banyak ruang yang harus disediakan untuk variabel tersebut di stack. Di sisi lain #definekita memiliki konstanta yang secara langsung dikonversi menjadi kode biner - betapapun lama - dan disimpan dalam memori apa adanya.
user106313
2
@ user31782 - tidak cukup. Ketika Anda mendeklarasikan variabel, tipe memberi tahu kompilator properti apa yang dimiliki variabel. Salah satu sifat itu adalah ukuran; properti lain termasuk bagaimana itu mewakili nilai dan operasi apa yang dapat dilakukan pada nilai-nilai itu.
Pete Becker
@PeteBecker Lalu bagaimana kompiler mengetahui properti-properti lain ini #define X 5.2?
user106313
1
Itu karena dengan memasukkan tipe yang salah kepada printfAnda memanggil perilaku yang tidak terdefinisi. Di komputer saya, cuplikan mencetak nilai yang berbeda setiap kali, pada Ideone macet setelah mencetak nol.
Matteo Italia
4
@ user31782 - "Sepertinya saya dapat melakukan operasi apa pun pada tipe data apa pun" Tidak. X*YTidak valid jika Xdan Yadalah pointer, tetapi tidak apa-apa jika itu ints; *Xtidak valid jika Xadalah int, tapi tidak apa-apa jika itu adalah pointer.
Pete Becker
4

X dalam contoh kedua tidak pernah mengambang. Itu disebut makro, itu menggantikan nilai makro yang didefinisikan 'X' di sumber dengan nilai. Artikel yang dapat dibaca di #define ada di sini .

Dalam hal kode yang disediakan, sebelum kompilasi preprocessor mengubah kode

Z=Y+X;

untuk

Z=Y+5.2;

dan itulah yang dikompilasi.

Itu berarti Anda juga dapat mengganti 'nilai' tersebut dengan kode seperti

#define X sqrt(Y)

atau bahkan

#define X Y
James Snell
sumber
3
Itu hanya disebut makro, bukan makro variadik. Makro variadik adalah makro yang mengambil sejumlah variabel argumen, misalnya #define FOO(...) { __VA_ARGS__ }.
hvd
2
Buruk saya, akan memperbaiki :)
James Snell
1

Jawaban singkatnya adalah tipe kebutuhan C karena sejarah / mewakili perangkat keras.

Sejarah: C dikembangkan pada awal 1970-an dan dimaksudkan sebagai bahasa untuk pemrograman sistem. Kode idealnya cepat dan memanfaatkan kapabilitas perangkat keras sebaik-baiknya.

Jenis inferring pada waktu kompilasi dimungkinkan, tetapi waktu kompilasi yang lambat akan meningkat (lihat kartun 'kompilasi' XKCD. Ini digunakan untuk 'hello world' selama setidaknya 10 tahun setelah C diterbitkan ). Jenis inferring pada saat runtime tidak akan cocok dengan tujuan pemrograman sistem. Runtime inferrence membutuhkan pustaka run time tambahan. C datang jauh sebelum PC pertama. Yang memiliki 256 RAM. Bukan Gigabytes atau Megabytes tetapi Kilobytes.

Dalam contoh Anda, jika Anda menghilangkan jenis

   X=5.2;
   Y=5.1;

   Z=Y+X;

Kemudian kompiler dapat dengan senang hati mengetahui bahwa X & Y adalah float dan membuat Z sama. Bahkan, kompiler modern juga akan berfungsi bahwa X & Y tidak diperlukan dan hanya mengatur Z ke 10.3.

Asumsikan bahwa perhitungan tertanam di dalam suatu fungsi. Penulis fungsi mungkin ingin menggunakan pengetahuan mereka tentang perangkat keras, atau masalah yang sedang dipecahkan.

Apakah dobel lebih cocok daripada pelampung? Membutuhkan lebih banyak memori dan lebih lambat tetapi akurasi hasilnya akan lebih tinggi.

Mungkin nilai kembalinya fungsi bisa int (atau panjang) karena desimal tidak penting, walaupun konversi dari float ke int bukan tanpa biaya.

Nilai kembali juga bisa dibuat ganda menjamin float + float tidak meluap.

Semua pertanyaan ini tampaknya tidak ada gunanya bagi sebagian besar kode yang ditulis hari ini, tetapi sangat penting ketika C diproduksi.

itj
sumber
1
ini tidak menjelaskan misalnya mengapa deklarasi tipe tidak dibuat opsional, yang memungkinkan programmer memilih untuk mendeklarasikannya secara eksplisit atau mengandalkan kompiler untuk menyimpulkan
gnat
1
Memang tidak @gnat. Saya telah mengubah teksnya tetapi tidak ada gunanya melakukannya pada saat itu. Domain C dirancang untuk benar-benar ingin memutuskan untuk menyimpan 17 dalam 1 byte, atau 2 byte atau 4 byte atau sebagai string atau sebagai 5 bit dalam sebuah kata.
itj
0

C tidak memiliki tipe inferensi (itulah yang disebut ketika kompiler menebak tipe variabel untuk Anda) karena sudah lama. Ini dikembangkan pada awal tahun 1970-an

Banyak bahasa yang lebih baru memiliki sistem yang memungkinkan Anda untuk menggunakan variabel tanpa menentukan jenisnya (ruby, javascript, python, dll.)

Tristan Burnside
sumber
12
Tak satu pun dari bahasa yang Anda sebutkan (Ruby, JS, Python) memiliki tipe inferensi sebagai fitur bahasa, meskipun implementasi dapat menggunakannya untuk meningkatkan efisiensi. Sebagai gantinya, mereka menggunakan pengetikan dinamis di mana nilai memiliki tipe, tetapi variabel atau ekspresi lainnya tidak.
amon
2
JS tidak memungkinkan Anda untuk menghapus jenis - itu hanya tidak memungkinkan Anda untuk mendeklarasikannya sama sekali. Menggunakan mengetik dinamis, di mana nilai-nilai memiliki jenis (misalnya trueadalah boolean), tidak variabel (misalnya var xmungkin berisi nilai dari jenis apa pun). Juga, ketik inferensi untuk kasus-kasus sederhana seperti yang dari pertanyaan mungkin telah diketahui satu dekade sebelum C dirilis.
scriptin
2
Itu tidak membuat pernyataan itu salah (untuk memaksakan sesuatu Anda juga harus mengizinkannya). Ketik inferensi yang ada tidak mengubah fakta bahwa sistem tipe C adalah hasil dari konteks historisnya (sebagai lawan dari alasan filosofis atau batasan teknis yang dinyatakan secara spesifik)
Tristan Burnside
2
Menimbang bahwa ML - yang hampir sama tuanya dengan C - memiliki inferensi tipe, "sudah tua" bukanlah penjelasan yang baik. Konteks di mana C digunakan dan dikembangkan (mesin kecil yang menuntut jejak sangat kecil untuk kompiler) tampaknya lebih mungkin. Tidak tahu mengapa Anda akan menyebutkan bahasa pengetikan dinamis alih-alih hanya beberapa contoh bahasa dengan inferensi tipe - Haskell, ML, heck C # memilikinya - bukan fitur yang tidak jelas lagi.
Voo
2
@BradS Fortran bukan contoh yang baik karena huruf pertama dari nama variabel adalah deklarasi tipe, kecuali jika Anda menggunakan implicit nonedalam hal ini Anda harus mendeklarasikan tipe.
dmckee