โครงสร้างที่รวดเร็วและกลายพันธุ์


101

มีบางอย่างที่ฉันไม่เข้าใจทั้งหมดเกี่ยวกับการกลายพันธุ์ประเภทค่าใน Swift

เนื่องจากสถานะ iBook "The Swift Programming Language": โดยค่าเริ่มต้นคุณสมบัติของประเภทค่าไม่สามารถแก้ไขได้จากวิธีการอินสแตนซ์

ดังนั้นเพื่อให้เป็นไปได้เราสามารถประกาศวิธีการโดยใช้mutatingคีย์เวิร์ดภายในโครงสร้างและ enums

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

struct Point {
    var x = 0, y = 0
    mutating func moveToX(x: Int, andY y:Int) { //Needs to be a mutating method in order to work
        self.x = x
        self.y = y
    }
}

var p = Point(x: 1, y: 2)
p.x = 3 //Works from outside the struct!
p.moveToX(5, andY: 5) 

มีใครทราบเหตุผลว่าทำไมโครงสร้างไม่สามารถเปลี่ยนเนื้อหาจากภายในบริบทของตนเองได้ในขณะที่เนื้อหาสามารถเปลี่ยนแปลงที่อื่นได้อย่างง่ายดาย?

คำตอบ:


85

แอตทริบิวต์ความผันแปรถูกทำเครื่องหมายบนหน่วยเก็บข้อมูล (ค่าคงที่หรือตัวแปร) ไม่ใช่ประเภท คุณสามารถคิด struct มีสองโหมด: ไม่แน่นอนและไม่เปลี่ยนรูป หากคุณกำหนดค่าโครงสร้างให้กับหน่วยเก็บข้อมูลที่ไม่เปลี่ยนรูป (เราเรียกมันว่าletหรือค่าคงที่ใน Swift) ค่าจะกลายเป็นโหมดไม่เปลี่ยนรูปและคุณไม่สามารถเปลี่ยนสถานะใด ๆ ในค่าได้ (รวมถึงการเรียกวิธีการกลายพันธุ์)

หากกำหนดค่าให้กับพื้นที่เก็บข้อมูลที่เปลี่ยนแปลงได้ (เราเรียกมันว่าvarหรือตัวแปรใน Swift) คุณมีอิสระที่จะแก้ไขสถานะของค่าเหล่านี้และอนุญาตให้เรียกใช้วิธีการกลายพันธุ์ได้

นอกจากนี้คลาสยังไม่มีโหมดไม่เปลี่ยนรูป / เปลี่ยนรูปแบบนี้ IMO เนื่องจากโดยทั่วไปแล้วคลาสจะใช้เพื่อแสดงเอนทิตีที่สามารถอ้างอิงได้ และเอนทิตีที่สามารถอ้างอิงได้มักจะเปลี่ยนแปลงได้เนื่องจากยากมากที่จะสร้างและจัดการกราฟอ้างอิงของเอนทิตีในลักษณะที่ไม่เปลี่ยนรูปด้วยประสิทธิภาพที่เหมาะสม พวกเขาอาจเพิ่มคุณสมบัตินี้ในภายหลัง แต่ไม่ใช่อย่างน้อยตอนนี้

สำหรับโปรแกรมเมอร์ Objective-C แนวคิดที่เปลี่ยนแปลงได้ / ไม่เปลี่ยนรูปเป็นสิ่งที่คุ้นเคยมาก ใน Objective-C เรามีคลาสที่แยกจากกันสองคลาสสำหรับแต่ละแนวคิด แต่ใน Swift คุณสามารถทำได้ด้วยโครงสร้างเดียว ทำงานครึ่งหนึ่ง

สำหรับโปรแกรมเมอร์ C / C ++ นี่เป็นแนวคิดที่คุ้นเคยเช่นกัน นี่คือสิ่งที่constคีย์เวิร์ดทำใน C / C ++

นอกจากนี้ค่าที่ไม่เปลี่ยนรูปยังสามารถปรับให้เหมาะสมได้เป็นอย่างดี ในทางทฤษฎีคอมไพเลอร์ Swift (หรือ LLVM) สามารถคัดลอกการคัดลอกค่าที่ส่งผ่านได้letเช่นเดียวกับใน C ++ หากคุณใช้โครงสร้างที่ไม่เปลี่ยนรูปอย่างชาญฉลาดมันจะมีประสิทธิภาพดีกว่าคลาสที่อ้างอิง

อัปเดต

ตามที่ @Joseph อ้างว่าสิ่งนี้ไม่ได้ให้เหตุผลฉันจึงเพิ่มอีกเล็กน้อย

โครงสร้างมีสองวิธี วิธีการธรรมดาและการกลายพันธุ์ ธรรมดาวิธีหมายถึงการเปลี่ยนรูป (หรือไม่ mutating) การแยกนี้มีขึ้นเพื่อรองรับการไม่เปลี่ยนรูปเท่านั้นความหมายที่เท่านั้น วัตถุในโหมดไม่เปลี่ยนรูปไม่ควรเปลี่ยนสถานะเลย

จากนั้นวิธีการที่ไม่เปลี่ยนรูปจะต้องรับประกันความไม่เปลี่ยนรูปแบบทางความหมายนี้นี้ ซึ่งหมายความว่าไม่ควรเปลี่ยนค่าภายในใด ๆ ดังนั้นคอมไพเลอร์จึงไม่อนุญาตการเปลี่ยนแปลงสถานะใด ๆ ของตัวมันเองในวิธีที่ไม่เปลี่ยนรูป ในทางตรงกันข้ามวิธีการกลายพันธุ์มีอิสระที่จะแก้ไขสถานะ

แล้วคุณอาจมีคำถามว่าทำไมไม่เปลี่ยนรูปเป็นค่าเริ่มต้น? นั่นเป็นเพราะมันยากมากที่จะทำนายสถานะในอนาคตของค่าที่กลายพันธุ์และมักจะกลายเป็นสาเหตุหลักของอาการปวดหัวและจุดบกพร่อง หลายคนเห็นพ้องกันว่าวิธีแก้ปัญหาคือการหลีกเลี่ยงสิ่งที่เปลี่ยนแปลงไม่ได้จากนั้นโดยค่าเริ่มต้นไม่เปลี่ยนรูปก็อยู่ในรายการสินค้าที่ต้องการมานานหลายทศวรรษในภาษาตระกูล C / C ++ และที่มาของมัน

ดูสไตล์การใช้งานล้วนๆใช้งานอย่างแท้จริงสำหรับรายละเอียดเพิ่มเติม อย่างไรก็ตามเรายังคงต้องการของที่เปลี่ยนแปลงได้เนื่องจากของที่ไม่เปลี่ยนรูปมีจุดอ่อนบางอย่างและการพูดคุยเกี่ยวกับสิ่งเหล่านี้ดูเหมือนจะไม่ตรงประเด็น

ฉันหวังว่านี่จะช่วยได้.


2
โดยพื้นฐานแล้วฉันสามารถตีความได้เช่น: โครงสร้างไม่ทราบล่วงหน้าว่ามันจะไม่แน่นอนหรือไม่เปลี่ยนรูปดังนั้นจึงถือว่าไม่เปลี่ยนรูปเว้นแต่จะระบุไว้แตกต่างกัน เห็นแบบนั้นเข้าท่าจริงๆ ถูกต้องหรือไม่
Mike Seghers

1
@MikeSeghers ฉันคิดว่ามันสมเหตุสมผล
eonil

3
แม้ว่านี่จะเป็นคำตอบที่ดีที่สุดจากสองคำตอบ แต่ฉันก็ไม่คิดว่าคำตอบนี้จะตอบว่าทำไมโดยค่าเริ่มต้นคุณสมบัติของประเภทค่าไม่สามารถแก้ไขได้จากวิธีอินสแตนซ์ คุณบอกใบ้ว่าเป็นเพราะโครงสร้างเริ่มต้นเป็นไม่เปลี่ยนรูป แต่ฉันไม่คิดว่าเป็นเรื่องจริง
Joseph Knight

4
@JosephKnight ไม่ใช่ว่าโครงสร้างเริ่มต้นจะไม่เปลี่ยนรูป เป็นวิธีการของโครงสร้างเริ่มต้นเป็นไม่เปลี่ยนรูป คิดว่ามันเหมือนกับ C ++ ด้วยสมมติฐานย้อนกลับ ในวิธีการ C ++ มีค่าเริ่มต้นเป็นค่าคงที่คุณต้องเพิ่ม a constลงในเมธอดอย่างชัดเจนหากคุณต้องการให้มี 'const this' และได้รับอนุญาตในอินสแตนซ์คงที่ (ไม่สามารถใช้วิธีการปกติได้หากอินสแตนซ์เป็น const) Swift ทำเช่นเดียวกันกับค่าเริ่มต้นที่ตรงกันข้าม: วิธีการมี 'const this' เป็นค่าเริ่มต้นและคุณจะทำเครื่องหมายเป็นอย่างอื่นหากต้องห้ามสำหรับอินสแตนซ์คงที่
ไฟล์อนาล็อก

3
@golddove ใช่แล้วทำไม่ได้ คุณไม่ควร คุณจำเป็นต้องสร้างหรือรับค่าเวอร์ชันใหม่เสมอและโปรแกรมของคุณจะต้องได้รับการออกแบบมาเพื่อใช้วิธีนี้โดยเจตนา คุณลักษณะนี้มีไว้เพื่อป้องกันการกลายพันธุ์โดยไม่ตั้งใจดังกล่าว อีกครั้งนี้เป็นหนึ่งในแนวคิดหลักของการเขียนโปรแกรมรูปแบบการทำงาน
eonil

26

โครงสร้างคือการรวมเขตข้อมูล หากอินสแตนซ์โครงสร้างเฉพาะเปลี่ยนแปลงไม่ได้ฟิลด์ของมันจะไม่แน่นอน หากอินสแตนซ์ไม่เปลี่ยนรูปฟิลด์ของมันจะไม่เปลี่ยนรูป ดังนั้นจึงต้องเตรียมประเภทโครงสร้างสำหรับความเป็นไปได้ที่ฟิลด์ของอินสแตนซ์เฉพาะใด ๆ อาจไม่แน่นอนหรือไม่เปลี่ยนรูป

เพื่อให้เมธอดโครงสร้างกลายพันธุ์ฟิลด์ของโครงสร้างพื้นฐานฟิลด์เหล่านั้นจะต้องไม่แน่นอน ถ้าเมธอดที่กลายพันธุ์ฟิลด์ของโครงสร้างพื้นฐานถูกเรียกใช้กับโครงสร้างที่ไม่เปลี่ยนรูปก็จะพยายามกลายพันธุ์ฟิลด์ที่ไม่เปลี่ยนรูป เนื่องจากไม่มีอะไรดีที่จะเกิดขึ้นได้จึงต้องห้ามการขอร้องเช่นนั้น

เพื่อให้บรรลุเป้าหมายนั้น Swift ได้แบ่งวิธีการจัดโครงสร้างออกเป็นสองประเภท ได้แก่ ประเภทที่ปรับเปลี่ยนโครงสร้างพื้นฐานดังนั้นจึงสามารถเรียกใช้บนอินสแตนซ์โครงสร้างที่เปลี่ยนแปลงได้เท่านั้นและผู้ที่ไม่ได้ปรับเปลี่ยนโครงสร้างพื้นฐานดังนั้นจึงควรเรียกใช้ได้ทั้งในอินสแตนซ์ที่เปลี่ยนแปลงได้และไม่เปลี่ยนรูป . การใช้งานครั้งหลังน่าจะบ่อยกว่าและเป็นค่าเริ่มต้น

โดยการเปรียบเทียบ. NET ในปัจจุบัน (ยัง!) ไม่มีวิธีการแยกแยะวิธีการโครงสร้างที่ปรับเปลี่ยนโครงสร้างจากที่ไม่ได้ แต่การเรียกใช้เมธอดโครงสร้างบนอินสแตนซ์โครงสร้างที่ไม่เปลี่ยนรูปจะทำให้คอมไพเลอร์สร้างสำเนาของอินสแตนซ์โครงสร้างที่ไม่แน่นอนปล่อยให้เมธอดทำอะไรก็ได้ตามที่ต้องการและทิ้งสำเนาเมื่อเมธอดเสร็จสิ้น สิ่งนี้มีผลในการบังคับให้คอมไพลเลอร์เสียเวลาในการคัดลอกโครงสร้างไม่ว่าวิธีการนั้นจะแก้ไขหรือไม่แม้ว่าการเพิ่มการดำเนินการคัดลอกจะแทบไม่เปลี่ยนสิ่งที่จะเป็นโค้ดที่ไม่ถูกต้องทางความหมายให้เป็นโค้ดที่ถูกต้องตามความหมาย มันจะทำให้โค้ดที่มีความหมายผิดทางเดียวเท่านั้น (การแก้ไขค่า "ไม่เปลี่ยนรูป") ผิดไปในทางอื่น (ทำให้โค้ดคิดว่ามันกำลังแก้ไขโครงสร้าง แต่ยกเลิกการเปลี่ยนแปลงที่พยายาม) การอนุญาตให้มีวิธีการ struct เพื่อระบุว่าพวกเขาจะแก้ไขโครงสร้างพื้นฐานหรือไม่สามารถขจัดความจำเป็นในการดำเนินการคัดลอกที่ไร้ประโยชน์และยังช่วยให้มั่นใจได้ว่าการใช้งานที่ผิดพลาดจะถูกตั้งค่าสถานะ


1
การเปรียบเทียบกับ. NET และตัวอย่างที่ไม่มีคีย์เวิร์ดกลายพันธุ์ทำให้ฉันเข้าใจได้ชัดเจน การให้เหตุผลว่าทำไมคีย์เวิร์ดที่กลายพันธุ์จะสร้างประโยชน์ใด ๆ
Binarian

22

ข้อควรระวัง: เงื่อนไขของคนธรรมดาข้างหน้า

คำอธิบายนี้ไม่ถูกต้องอย่างจริงจังในระดับรหัสที่สำคัญที่สุด อย่างไรก็ตามมีการตรวจสอบโดยผู้ชายที่ใช้งาน Swift จริงและเขาบอกว่ามันดีพอเป็นคำอธิบายพื้นฐาน

ดังนั้นผมจึงต้องการที่จะพยายามที่จะเพียงและโดยตรงตอบคำถามของ "ทำไม"

เพื่อความแม่นยำ: เหตุใดเราจึงต้องทำเครื่องหมายฟังก์ชันของโครงสร้างmutatingเมื่อเราสามารถเปลี่ยนพารามิเตอร์โครงสร้างโดยไม่ต้องแก้ไขคำสำคัญใด ๆ

ดังนั้นภาพรวมมันมีส่วนเกี่ยวข้องกับปรัชญาที่ทำให้ Swift swift

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

ในสถานการณ์เช่นนี้ Swift ก็เหมือนกับที่ทำการไปรษณีย์ หากผู้คนจำนวนมากที่มีรายชื่อติดต่อจำนวนมากย้ายไปรอบ ๆ จำนวนมากแสดงว่ามีค่าใช้จ่ายสูงมาก ต้องจ่ายพนักงานจำนวนมากเพื่อจัดการการแจ้งเตือนเหล่านั้นทั้งหมดและกระบวนการนี้ต้องใช้เวลาและความพยายามอย่างมาก นั่นเป็นเหตุผลที่สถานะในอุดมคติของ Swift คือให้ทุกคนในเมืองมีผู้ติดต่อให้น้อยที่สุด จากนั้นไม่จำเป็นต้องมีพนักงานจำนวนมากในการจัดการการเปลี่ยนแปลงที่อยู่และสามารถทำทุกอย่างได้เร็วขึ้นและดีขึ้น

นี่คือสาเหตุที่ Swift-folks ต่างพากันคลั่งไคล้ประเภทมูลค่าเทียบกับประเภทอ้างอิง โดยธรรมชาติแล้วประเภทการอ้างอิงจะเพิ่ม "รายชื่อติดต่อ" ไว้ทั่วทุกที่และประเภทค่ามักไม่ต้องการมากกว่าคู่ ประเภทค่าคือ "Swift" -er

กลับไปที่ภาพเล็ก: structs. โครงสร้างเป็นเรื่องใหญ่ใน Swift เพราะสามารถทำสิ่งต่างๆได้เกือบทั้งหมด แต่เป็นประเภทมูลค่า

Let 's ยังคงคล้ายคลึงอยู่ทางกายภาพโดยจินตนาการที่อาศัยอยู่ในmisterStruct someObjectVilleการเปรียบเทียบเริ่มขึ้นเล็กน้อยที่นี่ แต่ฉันคิดว่ามันยังมีประโยชน์

ดังนั้นในการสร้างแบบจำลองการเปลี่ยนตัวแปรใน a structสมมติว่าmisterStructมีผมสีเขียวและได้รับคำสั่งให้เปลี่ยนเป็นผมสีน้ำเงิน การเปรียบเทียบได้รับความสับสนเหมือนที่ฉันพูด แต่สิ่งที่เกิดขึ้นคือแทนที่จะเปลี่ยนmisterStructทรงผมคนเก่าก็ย้ายออกไปและคนใหม่ที่มีผมสีฟ้าก็ย้ายเข้ามาและคนใหม่คนนั้นก็เริ่มเรียกตัวเองmisterStructว่า ไม่มีใครต้องรับการแจ้งเปลี่ยนที่อยู่ แต่ถ้าใครดูที่อยู่นั้นก็จะเห็นผู้ชายผมสีฟ้า

ทีนี้ลองจำลองสิ่งที่เกิดขึ้นเมื่อคุณเรียกใช้ฟังก์ชันบนไฟล์struct. ในกรณีนี้ก็เหมือนกับmisterStructได้รับคำสั่งเช่นchangeYourHairBlue(). ไปรษณีย์จึงส่งคำสั่งให้misterStruct"ไปเปลี่ยนผมเป็นสีน้ำเงินแล้วบอกฉันว่าเสร็จเมื่อไร"

ถ้าเขาทำตามกิจวัตรเดิม ๆ เหมือนเดิมถ้าเขาทำในสิ่งที่ทำเมื่อตัวแปรเปลี่ยนไปโดยตรงสิ่งที่misterStructจะทำคือย้ายออกจากบ้านของเขาเองและโทรหาคนใหม่ที่มีผมสีฟ้า แต่นั่นคือปัญหา

คำสั่งคือ "ไปเปลี่ยนผมของคุณเป็นสีน้ำเงินแล้วบอกฉันเมื่อคุณทำเสร็จแล้ว" แต่เป็นคนชุดเขียวที่ได้รับคำสั่งนั้น หลังจากที่ผู้ชายสีฟ้าย้ายเข้ามาแล้วการแจ้งเตือน "งานเสร็จสมบูรณ์" ยังคงต้องส่งกลับไป แต่ผู้ชายสีฟ้าไม่รู้เรื่องเลย

[เพื่อให้เกิดการเปรียบเทียบที่น่ากลัวนี้จริงๆสิ่งที่เกิดขึ้นในทางเทคนิคกับผู้ชายผมสีเขียวคือหลังจากที่เขาย้ายออกไปเขาก็ฆ่าตัวตายทันที ดังนั้นเขาจึงไม่สามารถแจ้งใครได้ว่างานเสร็จแล้ว! ]

เพื่อหลีกเลี่ยงปัญหานี้ในกรณีเช่นนี้เท่านั้น , สวิฟท์ได้ไปโดยตรงไปที่บ้านตามที่อยู่ที่และจริงเปลี่ยนผมที่อยู่อาศัยในปัจจุบัน นั่นเป็นกระบวนการที่แตกต่างอย่างสิ้นเชิงกับการส่งผู้ชายคนใหม่

และนั่นคือเหตุผลที่ Swift ต้องการให้เราใช้mutatingคีย์เวิร์ด!

ผลลัพธ์ที่ได้ดูเหมือนกับสิ่งที่ต้องอ้างถึงโครงสร้าง: ผู้อยู่อาศัยในบ้านตอนนี้มีผมสีฟ้า แต่กระบวนการในการบรรลุเป้าหมายนั้นแตกต่างกันอย่างสิ้นเชิง ดูเหมือนว่ากำลังทำสิ่งเดียวกัน แต่มันทำสิ่งที่แตกต่างกันมาก มันกำลังทำสิ่งที่Swift Structs โดยทั่วไปไม่เคยทำ

ดังนั้นเพื่อให้คอมไพเลอร์ที่ไม่ดีช่วยเล็กน้อยและไม่ต้องคิดว่าฟังก์ชันกลายพันธุ์structหรือไม่ด้วยตัวมันเองสำหรับทุกฟังก์ชัน struct เดียวเราจึงขอให้สงสารและใช้mutatingคีย์เวิร์ด

โดยพื้นฐานแล้วเพื่อช่วยให้ Swift มีความรวดเร็วเราทุกคนต้องทำหน้าที่ของเรา :)


1
นี่คือ <f-word-here> เขียนขึ้นอย่างน่าอัศจรรย์! ดังนั้นเข้าใจง่าย ฉันได้ท่องอินเทอร์เน็ตเพื่อสิ่งนี้และนี่คือคำตอบ (ดีโดยไม่ต้องผ่านซอร์สโค้ดเลย) ขอบคุณมากที่สละเวลาเขียน
Vaibhav

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

@Teo ฉันหวังว่าฉันจะตอบได้อย่างชัดเจน แต่สิ่งที่ดีที่สุดที่ฉันสามารถพูดได้ก็คือฉันใช้การเปรียบเทียบนี้ผ่านนักพัฒนา Swift ตัวจริงและพวกเขาบอกว่า "ถูกต้องโดยพื้นฐาน" ซึ่งหมายความว่ามีอะไรมากกว่านั้นในแง่เทคนิค แต่ด้วยความเข้าใจนั้น - มันมีอะไรมากกว่านี้ - ในความหมายกว้าง ๆ ใช่นั่นคือสิ่งที่การเปรียบเทียบนี้พูด
Le Mot Juiced

8

โครงสร้าง Swift สามารถสร้างอินสแตนซ์เป็นค่าคงที่ (ผ่านlet) หรือตัวแปร (ผ่านvar)

พิจารณาโครงสร้างของ Swift Array(ใช่มันเป็นโครงสร้าง)

var petNames: [String] = ["Ruff", "Garfield", "Nemo"]
petNames.append("Harvey") // ["Ruff", "Garfield", "Nemo", "Harvey"]

let planetNames: [String] = ["Mercury", "Venus", "Earth", "Mars", "Jupiter", "Saturn", "Uranus", "Neptune"]
planetNames.append("Pluto") //Error, sorry Pluto. No can do

เหตุใดการต่อท้ายจึงไม่ทำงานกับชื่อดาวเคราะห์ เนื่องจากต่อท้ายมีmutatingคำหลักกำกับอยู่ และนับตั้งแต่planetNamesมีการประกาศโดยใช้letวิธีการทั้งหมดที่ทำเครื่องหมายไว้จึงไม่อยู่ในขีด จำกัด

ในตัวอย่างของคุณคอมไพลเลอร์สามารถบอกได้ว่าคุณกำลังปรับเปลี่ยนโครงสร้างโดยกำหนดคุณสมบัติอย่างน้อยหนึ่งอย่างนอกinit. หากคุณเปลี่ยนรหัสของคุณเล็กน้อยคุณจะเห็นสิ่งนั้นxและyไม่สามารถเข้าถึงได้จากภายนอกโครงสร้าง ให้สังเกตletในบรรทัดแรก

let p = Point(x: 1, y: 2)
p.x = 3 //error
p.moveToX(5, andY: 5) //error

5

พิจารณาการเปรียบเทียบกับ C ++ วิธี struct ในสวิฟท์เป็นอยู่mutating/ not- mutatingจะคล้ายคลึงกับวิธีการใน C ++ เป็นไม่ใช่/const constวิธีการที่ทำเครื่องหมายconstใน C ++ ในทำนองเดียวกันไม่สามารถเปลี่ยนโครงสร้างได้

คุณสามารถเปลี่ยนตัวแปรจากภายนอกโครงสร้างได้ แต่คุณไม่สามารถเปลี่ยนตัวแปรจากวิธีการของมันเองได้

ใน C ++ คุณยังสามารถ "เปลี่ยน var จากนอก struct ว่า" - แต่เพียงถ้าคุณมีไม่ใช่constตัวแปร struct หากคุณมีconstตัวแปร struct คุณจะไม่สามารถกำหนดให้กับ var และคุณไม่สามารถเรียกใช้constวิธีที่ไม่ใช่ ในทำนองเดียวกันใน Swift คุณสามารถเปลี่ยนคุณสมบัติของโครงสร้างได้เฉพาะในกรณีที่ตัวแปร struct ไม่ใช่ค่าคงที่ หากคุณมีค่าคงที่ของโครงสร้างคุณจะไม่สามารถกำหนดให้กับคุณสมบัติและคุณไม่สามารถเรียกใช้mutatingเมธอดได้


1

ฉันสงสัยในสิ่งเดียวกันเมื่อฉันเริ่มเรียนรู้ Swift และคำตอบแต่ละข้อเหล่านี้ในขณะที่การเพิ่มข้อมูลเชิงลึกบางทีอาจจะเป็นคำพูดและสับสนในสิทธิของพวกเขาเอง ฉันคิดว่าคำตอบสำหรับคำถามของคุณนั้นค่อนข้างง่าย ...

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

ในทางตรงกันข้ามการตั้งค่าคุณสมบัติจากภายนอกโครงสร้างของคุณกำลังทำงานบนอินสแตนซ์ที่รู้จักของโครงสร้างนั้น หากกำหนดให้เป็นค่าคงที่ Xcode จะแจ้งให้คุณทราบทันทีที่คุณพิมพ์ในการเรียกใช้เมธอด

นี่เป็นหนึ่งในสิ่งที่ฉันชื่นชอบเกี่ยวกับ Swift เมื่อฉันเริ่มใช้มันมากขึ้น - ได้รับการแจ้งเตือนถึงข้อผิดพลาดขณะที่ฉันพิมพ์ แน่นอนว่าจะเอาชนะการแก้ไขข้อบกพร่องของ JavaScript ที่คลุมเครือ!


1

SWIFT: การใช้ฟังก์ชันการกลายพันธุ์ในโครงสร้าง

โปรแกรมเมอร์ Swift ได้พัฒนา Structs ในลักษณะที่คุณสมบัติของมันไม่สามารถแก้ไขได้ภายในวิธีการ Struct ตัวอย่างเช่นตรวจสอบรหัสที่ระบุด้านล่าง

struct City
{
  var population : Int 
  func changePopulation(newpopulation : Int)
  {
      population = newpopulation //error: cannot modify property "popultion"
  }
}
  var mycity = City(population : 1000)
  mycity.changePopulation(newpopulation : 2000)

ในการรันโค้ดด้านบนเราได้รับข้อผิดพลาดเนื่องจากเรากำลังพยายามกำหนดค่าใหม่ให้กับประชากรคุณสมบัติของ Struct City โดยค่าเริ่มต้นคุณสมบัติโครงสร้างไม่สามารถกลายพันธุ์ภายในวิธีการของตัวเอง นี่คือวิธีที่นักพัฒนาของ Apple สร้างขึ้นเพื่อให้ Structs มีลักษณะคงที่โดยค่าเริ่มต้น

เราจะแก้อย่างไร ทางเลือกคืออะไร?

คำหลักที่กลายพันธุ์:

การประกาศฟังก์ชันเป็นการกลายพันธุ์ภายในโครงสร้างทำให้เราสามารถเปลี่ยนแปลงคุณสมบัติในโครงสร้างได้ บรรทัดที่: 5 จากโค้ดด้านบนเปลี่ยนเป็นแบบนี้

mutating changePopulation(newpopulation : Int)

ตอนนี้เราสามารถกำหนดค่าของ newpopulationคุณสมบัติที่ประชากรจะอยู่ในขอบเขตของวิธีการ

บันทึก :

let mycity = City(1000)     
mycity.changePopulation(newpopulation : 2000)   //error: cannot modify property "popultion"

หากเราใช้ let แทน var สำหรับออบเจ็กต์ Struct เราจะไม่สามารถกลายพันธุ์ค่าของคุณสมบัติใด ๆ ได้นอกจากนี้นี่คือสาเหตุที่เราได้รับข้อผิดพลาดเมื่อเราพยายามเรียกใช้ฟังก์ชันการกลายพันธุ์โดยใช้ let instance ดังนั้นจึงเป็นการดีกว่าที่จะใช้ var เมื่อใดก็ตามที่คุณเปลี่ยนมูลค่าของคุณสมบัติ

ชอบที่จะรับฟังความคิดเห็นและความคิดของคุณ… ..


0

อีกหนึ่งตัวแปร

struct MyStruct {
    var myVar = "myVar"
    let myLet = "myLet"
}

func testMutateString() {
    //given
    let myStruct = MyStruct()
    
    //Change var
    //when
    var myStructCopy = myStruct
    myStructCopy.myVar = "myVar changed 1"
    
    //then
    XCTAssert(myStructCopy.myVar == "myVar changed 1")
    
    //Change let
    //when
    withUnsafeMutableBytes(of: &myStructCopy) { bytes in
        let offset = MemoryLayout.offset(of: \MyStruct.myLet)!
        let rawPointerToValue = bytes.baseAddress! + offset
        let pointerToValue = rawPointerToValue.assumingMemoryBound(to: String.self)
        pointerToValue.pointee = "myLet changed"
    }
    
    //then
    XCTAssert(myStructCopy.myLet == "myLet changed")
}

[ประเภท Swift]

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.