ทำไมเราต้องพูดถึงชนิดข้อมูลของตัวแปรใน C


19

โดยปกติใน C เราต้องบอกคอมพิวเตอร์ถึงชนิดของข้อมูลในการประกาศตัวแปร เช่นในโปรแกรมต่อไปนี้ฉันต้องการพิมพ์ผลรวมของตัวเลขทศนิยมสองตัว X และ Y

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

}

ฉันต้องบอกคอมไพเลอร์ถึงชนิดของตัวแปร X

  • คอมไพเลอร์ไม่สามารถระบุประเภทของXตัวเองได้หรือไม่

ใช่มันสามารถทำได้ถ้าฉันทำสิ่งนี้:

#define X 5.2

ตอนนี้ฉันสามารถเขียนโปรแกรมโดยไม่บอกคอมไพเลอร์Xว่าเป็น:

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

}  

ดังนั้นเราจะเห็นว่าภาษา C มีคุณสมบัติบางอย่างที่ใช้ซึ่งสามารถกำหนดประเภทของข้อมูลด้วยตัวเอง ในกรณีของฉันมันกำหนดว่าXเป็นประเภทลอย

  • ทำไมเราต้องพูดถึงประเภทของข้อมูลเมื่อเราประกาศบางสิ่งใน main ()? ทำไมถึงไม่สามารถคอมไพเลอร์กำหนดชนิดข้อมูลของตัวแปรในตัวของมันเองเช่นเดียวกับในmain()#define

14
จริงๆแล้วทั้งสองโปรแกรมนั้นไม่เทียบเท่ากันเพราะมันอาจให้ผลต่างกันเล็กน้อย! 5.2คือ a doubleดังนั้นโปรแกรมแรกปัดเศษตัวอักษรคู่ให้floatมีความแม่นยำจากนั้นเพิ่มเป็นลอยในขณะที่รอบที่สองเป็นตัวแทนคู่ของ 5.1 กลับไปdoubleและเพิ่มลงในdoubleค่า 5.2 โดยใช้การdoubleเพิ่มแล้วปัดผลลัพธ์ของการคำนวณนั้นเป็นfloatความแม่นยำ . เนื่องจากการปัดเศษเกิดขึ้นในสถานที่ต่าง ๆ ผลลัพธ์อาจ dffer นี่เป็นเพียงหนึ่งตัวอย่างสำหรับประเภทของตัวแปรที่มีผลต่อพฤติกรรมของโปรแกรมที่เหมือนกัน

12
เมื่อคุณทำ#define X 5.2, Xไม่ได้เป็นตัวแปร แต่คงที่ดังนั้นมันจะถูกแทนที่แท้จริงจะ preprocessor กับทุกที่ที่คุณกล่าวถึง5.2 คุณไม่สามารถโอนสิทธิX X
scriptin

16
เช่นเดียวกับหมายเหตุ: นี่เป็นพรและคำสาป ในอีกด้านหนึ่งคุณต้องพิมพ์อักขระสองสามตัวเมื่อคอมไพเลอร์สามารถทำได้จริง ๆ สำหรับคุณ (C ++ autoทำในสิ่งที่คุณต้องการจริงๆ) ในทางกลับกันถ้าคุณคิดว่าคุณรู้ว่าโค้ดของคุณทำอะไรอยู่และคุณพิมพ์อะไรอย่างอื่นการพิมพ์แบบคงที่จะทำให้เกิดข้อผิดพลาดก่อนหน้านี้ก่อนที่มันจะกลายเป็นปัญหาใหญ่ ทุกภาษามีความสมดุล: การพิมพ์แบบคงที่, พิมพ์ต่ำ, พิมพ์แบบไดนามิก สำหรับงานบางอย่างการพิมพ์พิเศษนั้นคุ้มค่าจริง ๆ สำหรับคนอื่น ๆ มันเป็นการสิ้นเปลือง
Cort Ammon - Reinstate Monica

เรียนรู้ Ocaml และ / หรือ Haskell .... คุณจะมีความสุขกับความสามารถในการอนุมานประเภทของพวกเขา
Basile Starynkevitch

คำตอบ:


46

คุณกำลังเปรียบเทียบการประกาศตัวแปรกับ#defines ซึ่งไม่ถูกต้อง ด้วย a #defineคุณสร้างการแมประหว่างตัวระบุและข้อมูลโค้ดของซอร์สโค้ด ตัวประมวลผลล่วงหน้า C จะแทนที่เหตุการณ์ที่เกิดขึ้นของตัวระบุนั้นอย่างแท้จริงด้วยข้อมูลโค้ดที่ระบุ การเขียน

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

จบลงด้วยการเป็นสิ่งเดียวกันกับผู้แปล

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

คิดว่าเป็นสำเนาและวางอัตโนมัติ

นอกจากนี้ตัวแปรปกติสามารถกำหนดใหม่ได้ในขณะที่แมโครที่สร้างขึ้นด้วย#defineไม่สามารถ (แม้ว่าคุณสามารถ re- #defineit) การแสดงออกFOO = 7จะเป็นข้อผิดพลาดของคอมไพเลอร์เนื่องจากเราไม่สามารถกำหนดให้กับ "ค่า": 40 + 2 = 7ผิดกฎหมาย

แล้วทำไมเราถึงต้องการประเภททั้งหมด เห็นได้ชัดว่าบางภาษากำจัดประเภทนี้โดยเฉพาะอย่างยิ่งในภาษาสคริปต์ อย่างไรก็ตามพวกเขามักจะมีสิ่งที่เรียกว่า "การพิมพ์แบบไดนามิก" โดยที่ตัวแปรไม่มีประเภทคงที่ แต่มีค่า ขณะนี้มีความยืดหยุ่นมากกว่า แต่ก็มีประสิทธิภาพน้อยกว่า C ชอบประสิทธิภาพดังนั้นจึงมีแนวคิดที่ง่ายและมีประสิทธิภาพของตัวแปร:

มีหน่วยความจำที่เรียกว่า "สแต็ค" ตัวแปรโลคัลแต่ละตัวสอดคล้องกับพื้นที่บนสแต็ก ทีนี้คำถามคือพื้นที่นี้ต้องมีกี่ไบต์? ใน C แต่ละประเภทมีขนาดที่กำหนดไว้อย่างดีซึ่งคุณสามารถสืบค้นsizeof(type)ได้ คอมไพเลอร์จำเป็นต้องทราบชนิดของตัวแปรแต่ละตัวเพื่อให้สามารถจองพื้นที่บนสแต็กได้อย่างถูกต้อง

ทำไมค่าคงที่ที่สร้างโดยไม่#defineต้องการคำอธิบายประกอบประเภท? พวกเขาจะไม่ถูกเก็บไว้ในกอง ให้#defineสร้างตัวอย่างซอร์สโค้ดที่สามารถใช้ซ้ำได้ในลักษณะที่สามารถบำรุงรักษาได้มากกว่าการคัดลอกและวาง ตัวอักษรในซอร์สโค้ดเช่น"foo"หรือ42.87ถูกเก็บโดยคอมไพเลอร์ไม่ว่าจะเป็นอินไลน์เป็นคำสั่งพิเศษหรือในส่วนข้อมูลแยกต่างหากของไบนารีผลลัพธ์

อย่างไรก็ตามตัวอักษรมีประเภท char *ตัวอักษรเป็นสตริง 42เป็นintแต่ยังสามารถใช้สำหรับประเภทที่สั้นกว่า (การแปลงให้แคบลง) จะเป็น42.8 doubleหากคุณมีตัวอักษรและต้องการให้มีประเภทที่แตกต่างกัน (เช่นเพื่อให้หรือ) แล้วคุณสามารถใช้คำต่อท้าย - จดหมายหลังจากที่แท้จริงว่าการเปลี่ยนแปลงวิธีการคอมไพเลอร์ที่ถือว่าตัวอักษร ในกรณีที่เราอาจจะพูดหรือ42.8float42unsigned long int42.8f42ul

บางภาษามีการพิมพ์แบบคงที่ใน C แต่คำอธิบายประกอบประเภทเป็นตัวเลือก ตัวอย่างคือ ML, Haskell, Scala, C #, C ++ 11 และ Go มันทำงานอย่างไร มายากล? ไม่สิ่งนี้เรียกว่า "การอนุมานประเภท" ใน C # and Go คอมไพเลอร์มองไปทางด้านขวามือของการมอบหมายและอนุมานประเภทของสิ่งนั้น 42ulนี้ค่อนข้างตรงไปตรงมาถ้าด้านขวามือเป็นตัวอักษรเช่น จากนั้นจะเห็นได้ชัดว่าชนิดของตัวแปรควรเป็นอย่างไร ภาษาอื่น ๆ ยังมีอัลกอริทึมที่ซับซ้อนมากขึ้นซึ่งคำนึงถึงวิธีการใช้ตัวแปร เช่นถ้าคุณทำx/2แล้วxไม่สามารถเป็นสตริงได้ แต่ต้องมีตัวเลขบางประเภท


ขอบคุณสำหรับการอธิบาย สิ่งที่ฉันเข้าใจคือเมื่อเราประกาศประเภทของตัวแปร (ท้องถิ่นหรือทั่วโลก) เรากำลังบอกคอมไพเลอร์จริง ๆ แล้วมันควรมีพื้นที่ว่างเท่าไรสำหรับตัวแปรนั้นในสแต็ก ในอีกทางหนึ่ง#defineเรากำลังมีค่าคงที่ซึ่งถูกแปลงเป็นรหัสไบนารี่โดยตรง - แต่อาจจะนาน - และมันจะถูกเก็บไว้ในหน่วยความจำตามที่มันเป็น
user106313

2
@ user31782 - ไม่มาก เมื่อคุณประกาศตัวแปรชนิดนี้จะบอกคอมไพเลอร์ว่าคุณสมบัติใดที่ตัวแปรนั้นมี หนึ่งในคุณสมบัติเหล่านั้นคือขนาด คุณสมบัติอื่น ๆ ได้แก่ วิธีแสดงค่าและการดำเนินการใดที่สามารถดำเนินการกับค่าเหล่านั้นได้
Pete Becker

@PeteBecker แล้วคอมไพเลอร์รู้จักคุณสมบัติอื่น ๆ เหล่านี้#define X 5.2อย่างไร
user106313

1
นั่นเป็นเพราะการส่งประเภทที่ไม่ถูกต้องให้printfคุณเรียกใช้พฤติกรรมที่ไม่ได้กำหนด บนเครื่องของฉันข้อมูลโค้ดพิมพ์ค่าต่างกันในแต่ละครั้งบนไอดีโอมันจะเกิดปัญหาหลังจากพิมพ์ศูนย์
Matteo Italia

4
@ user31782 - "ดูเหมือนว่าฉันสามารถดำเนินการกับชนิดข้อมูลใด ๆ " X*Yไม่ไม่ถูกต้องถ้าXและYเป็นตัวชี้ แต่ก็ไม่เป็นไรถ้าพวกเขาintเป็น; *Xไม่ถูกต้องถ้าXเป็นintแต่มันก็โอเคถ้ามันเป็นตัวชี้
Pete Becker

4

X ในตัวอย่างที่สองไม่เคยลอย มันถูกเรียกว่าแมโครมันจะแทนที่ค่าแมโครที่กำหนด 'X' ในแหล่งที่มาด้วยค่า บทความที่อ่านได้ใน #define เป็นที่นี่

ในกรณีของรหัสที่ให้มาก่อนการรวบรวมตัวประมวลผลล่วงหน้าจะเปลี่ยนรหัส

Z=Y+X;

ถึง

Z=Y+5.2;

และนั่นคือสิ่งที่ได้รับการรวบรวม

ซึ่งหมายความว่าคุณสามารถแทนที่ 'ค่า' ด้วยรหัสเช่น

#define X sqrt(Y)

หรือแม้กระทั่ง

#define X Y

3
มันถูกเรียกว่าแมโครไม่ใช่มาโครแบบ variadic แมโคร variadic #define FOO(...) { __VA_ARGS__ }เป็นแมโครที่ใช้เวลาจำนวนตัวแปรของการขัดแย้งเช่น
hvd

2
ไม่ดีจะแก้ไข :)
Snell James

1

คำตอบสั้น ๆ คือ C ต้องการชนิดเนื่องจากมีประวัติ / เป็นตัวแทนของฮาร์ดแวร์

ประวัติความเป็นมา: C ได้รับการพัฒนาในต้นปี 1970 และตั้งใจเป็นภาษาสำหรับการเขียนโปรแกรมระบบ รหัสเป็นไปอย่างรวดเร็วและใช้ประโยชน์จากความสามารถของฮาร์ดแวร์ได้อย่างดีที่สุด

แต่เวลาในการคอมไพล์ช้ากว่าจะเพิ่มขึ้น (อ้างอิงจากการ์ตูน 'การคอมไพล์' ของ XKCD สิ่งนี้เคยใช้กับ 'hello world' เป็นเวลาอย่างน้อย 10 ปีหลังจาก C ถูกเผยแพร่ ) การอนุมานชนิดที่รันไทม์จะไม่สอดคล้องกับวัตถุประสงค์ของการเขียนโปรแกรมระบบ Runtime inferrence ต้องการไลบรารีเวลาทำงานเพิ่มเติม C มานานก่อนพีซีเครื่องแรก ซึ่งมี 256 RAM ไม่ใช่กิกะไบต์หรือเมกะไบต์ แต่เป็นกิโลไบต์

ในตัวอย่างของคุณหากคุณไม่ระบุประเภท

   X=5.2;
   Y=5.1;

   Z=Y+X;

จากนั้นคอมไพเลอร์สามารถทำงานได้อย่างมีความสุขว่า X & Y เป็นแบบลอยตัวและทำให้ Z เป็นแบบเดียวกัน ในความเป็นจริงคอมไพเลอร์สมัยใหม่จะทำงานด้วยว่า X & Y ไม่จำเป็นและเพียงแค่ตั้งค่า Z เป็น 10.3

สมมติว่าการคำนวณนั้นฝังอยู่ภายในฟังก์ชัน ผู้เขียนฟังก์ชันอาจต้องการใช้ความรู้เกี่ยวกับฮาร์ดแวร์หรือปัญหาที่แก้ไข

คู่จะเหมาะสมกว่าการลอยหรือไม่ ใช้หน่วยความจำมากขึ้นและช้าลง แต่ความแม่นยำของผลลัพธ์จะสูงขึ้น

บางทีค่าส่งคืนของฟังก์ชันอาจเป็น int (หรือยาว) เพราะทศนิยมไม่สำคัญแม้ว่าการแปลงจาก float เป็น int ไม่ได้โดยไม่มีค่าใช้จ่าย

ค่าส่งคืนสามารถทำซ้ำได้ด้วยการรับประกันสองครั้งว่า float + float ไม่ล้น

คำถามทั้งหมดเหล่านี้ดูเหมือนไม่มีจุดหมายสำหรับรหัสส่วนใหญ่ที่เขียนในวันนี้ แต่มีความสำคัญเมื่อ C ถูกสร้างขึ้น


1
สิ่งนี้ไม่ได้อธิบายเช่นเหตุใดการประกาศประเภทจึงไม่เป็นทางเลือกอนุญาตให้โปรแกรมเมอร์เลือกที่จะประกาศอย่างชัดเจนหรือพึ่งพาคอมไพเลอร์เพื่ออนุมาน
gnat

1
แน่นอนว่ามันไม่ได้ @gnat ฉันได้ปรับแต่งข้อความ แต่ไม่มีเวลาทำเช่นนั้นในเวลา โดเมน C ถูกออกแบบมาเพื่อต้องการตัดสินใจจัดเก็บ 17 ใน 1 ไบต์หรือ 2 ไบต์หรือ 4 ไบต์หรือเป็นสตริงหรือ 5 บิตภายในคำ
itj

0

C ไม่มีการอนุมานประเภท (นั่นคือสิ่งที่มันถูกเรียกเมื่อคอมไพเลอร์เดาประเภทของตัวแปรสำหรับคุณ) เพราะมันเก่า ได้รับการพัฒนาในต้นปี 1970

ภาษาที่ใหม่กว่าหลายภาษามีระบบที่อนุญาตให้คุณใช้ตัวแปรโดยไม่ต้องระบุประเภทของภาษา (ruby, javascript, python ฯลฯ )


12
ไม่มีภาษาที่คุณพูดถึง (Ruby, JS, Python) ที่มีการอนุมานประเภทเป็นคุณสมบัติภาษาแม้ว่าการใช้งานอาจใช้เพื่อเพิ่มประสิทธิภาพ แต่ใช้การพิมพ์แบบไดนามิกโดยที่ค่ามีชนิด แต่ตัวแปรหรือนิพจน์อื่นไม่ทำงาน
amon

2
JS ไม่อนุญาตให้คุณละเว้นประเภทนี้ - เพียง แต่ไม่อนุญาตให้คุณประกาศสิ่งใด ๆ มันใช้การพิมพ์แบบไดนามิกที่ค่ามีประเภท (เช่นtrueคือboolean) ไม่ใช่ตัวแปร (เช่นvar xอาจมีค่าประเภทใด ๆ ) นอกจากนี้การอนุมานประเภทสำหรับกรณีง่าย ๆ อย่างเช่นจากคำถามอาจทราบกันมานานกว่าทศวรรษก่อนที่ C จะถูกปล่อยออกมา
scriptin

2
ที่ไม่ได้ทำให้คำสั่งที่เป็นเท็จ (เพื่อบังคับให้บางสิ่งบางอย่างคุณต้องอนุญาตให้มัน) การอนุมานประเภทที่มีอยู่ไม่ได้เปลี่ยนความจริงที่ว่าระบบประเภทของ C เป็นผลมาจากบริบททางประวัติศาสตร์ของมัน (เมื่อเทียบกับเหตุผลเชิงปรัชญาที่ระบุไว้โดยเฉพาะหรือข้อ จำกัด ทางเทคนิค)
Tristan Burnside

2
เมื่อพิจารณาว่า ML - ซึ่งค่อนข้างเก่าเท่ากับ C - มีการอนุมานประเภท "มันเก่า" ไม่ใช่คำอธิบายที่ดี บริบทที่ใช้และพัฒนา C (เครื่องจักรขนาดเล็กที่ต้องการรอยขนาดเล็กมากสำหรับคอมไพเลอร์) ดูเหมือนมีแนวโน้มมากขึ้น ไม่มีความคิดว่าทำไมคุณถึงพูดภาษาพิมพ์แบบไดนามิกแทนที่จะเป็นเพียงบางตัวอย่างของภาษาที่มีการอนุมานประเภท - Haskell, ML, heck C #มีมัน - คุณลักษณะที่ไม่ชัดเจนอีกต่อไป
Voo

2
@BradS Fortran ไม่ได้เป็นตัวอย่างที่ดีเพราะตัวอักษรตัวแรกของชื่อตัวแปรคือการประกาศชนิดเว้นแต่คุณจะใช้implicit noneในกรณีที่คุณต้องประกาศประเภท
dmckee
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.