ทำไมหลายภาษาจึงผ่านค่า?


36

แม้แต่ภาษาที่คุณมีการจัดการตัวชี้อย่างชัดเจนเช่น C มันจะถูกส่งผ่านตามค่าเสมอ (คุณสามารถส่งต่อโดยการอ้างอิง แต่นั่นไม่ใช่พฤติกรรมเริ่มต้น)

ประโยชน์ของสิ่งนี้คืออะไรทำไมมีหลายภาษาที่ผ่านค่านิยมและทำไมคนอื่นถึงผ่านอ้างอิง ? (ฉันเข้าใจว่า Haskell ผ่านการอ้างอิงถึงแม้ว่าฉันไม่แน่ใจ)


5
void acceptEntireProgrammingLanguageByValue(C++);
Thomas Eding

4
มันอาจจะยิ่งเลวร้าย. บางภาษาเก่ายังอนุญาตให้โทรตามชื่อ
hugomg

25
ที่จริงแล้วใน C คุณไม่สามารถผ่านการอ้างอิงได้ คุณสามารถส่งต่อตัวชี้ตามค่าซึ่งคล้ายกับการอ้างอิงโดยการอ้างอิง แต่ไม่เหมือนกัน อย่างไรก็ตามใน C ++ คุณสามารถผ่านการอ้างอิงได้
Mason Wheeler

@Mason Wheeler คุณสามารถอธิบายเพิ่มเติมหรือเพิ่มลิงก์บางส่วนได้เพราะคำสั่งของคุณไม่ชัดเจนสำหรับฉันเนื่องจากฉันไม่ใช่กูรู C / C ++ ซึ่งเป็นโปรแกรมเมอร์ธรรมดาขอบคุณ
Betlista

1
@Betlista: ด้วยการอ้างอิงผ่านคุณสามารถเขียนรูทีน swap ที่มีลักษณะดังนี้: temp := a; a := b; b := temp;และเมื่อมันคืนค่าของaและbจะถูกสลับ ไม่มีวิธีการทำเช่นนั้นใน C; คุณต้องผ่านพอยน์เตอร์ไปaและbและswapกิจวัตรต้องดำเนินการกับค่าที่ชี้ไป
Mason Wheeler

คำตอบ:


58

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

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

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


4
+1 อาร์เรย์ที่มีการพอยน์เตอร์ให้กับพอยน์เตอร์มีข้อยกเว้นที่น่าสังเกต แต่คำอธิบายโดยรวมนั้นดี
dasblinkenlight

6
@dasblinkenlight: อาร์เรย์เป็นจุดที่เจ็บใน C :(
Matthieu M.

55

Call-by-value และ call-by-reference เป็นเทคนิคการใช้งานที่ผิดพลาดสำหรับโหมดผ่านพารามิเตอร์เมื่อนานมาแล้ว

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

ALGOL เกิดขึ้นกับ call-by-name และ call-by-value Call-by-value ใช้สำหรับสิ่งที่ไม่ควรเปลี่ยน (พารามิเตอร์อินพุต) การโทรตามชื่อใช้สำหรับพารามิเตอร์เอาต์พุต การโทรตามชื่อกลายเป็นเรื่องสำคัญมากและ ALGOL 68 ก็ทำเช่นนั้น

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

PASCAL เพิ่มตัวชี้ไปยังพจนานุกรมการออกแบบภาษา

C จัดเตรียมการเรียกใช้โดยค่าและจำลองการโทรโดยอ้างอิงโดยการกำหนดตัวดำเนินการ kludge เพื่อส่งคืนตัวชี้ไปยังวัตถุที่กำหนดเองในหน่วยความจำ

ภาษาต่อมาคัดลอก C ส่วนใหญ่เป็นเพราะนักออกแบบไม่เคยเห็นอะไรเลย นี่อาจเป็นสาเหตุที่ค่าโทรโดยค่านิยมมาก

C ++ เพิ่ม kludge ที่ด้านบนของ C kludge เพื่อให้การโทรโดยอ้างอิง

ตอนนี้เป็นผลโดยตรงของการโทรโดยค่าเทียบกับการโทรโดยการอ้างอิงกับการโทรโดยตัวชี้ kludge, C และ C ++ (โปรแกรมเมอร์) มีอาการปวดหัวที่น่ากลัวกับตัวชี้ const และตัวชี้ไปยัง const (อ่านอย่างเดียว) วัตถุ

Ada พยายามหลีกเลี่ยงฝันร้ายทั้งหมดนี้

Ada ไม่มี call-by-value และ call-by-reference อย่างชัดเจน ค่อนข้าง Ada มีพารามิเตอร์ (ซึ่งอาจจะอ่าน แต่ไม่ได้เขียน), พารามิเตอร์ out (ซึ่งจะต้องเขียนก่อนที่จะสามารถอ่านได้) และพารามิเตอร์ out ซึ่งอาจอ่านและเขียนในลำดับใดก็ได้ คอมไพเลอร์ตัดสินใจว่าพารามิเตอร์เฉพาะถูกส่งผ่านโดยค่าหรือโดยการอ้างอิง: มันโปร่งใสให้โปรแกรมเมอร์


1
+1 นี่เป็นคำตอบเดียวที่ฉันได้เห็นที่นี่จนถึงตอนนี้ที่จริงตอบคำถามและทำให้รู้สึกและไม่ได้ใช้มันเป็นข้อแก้ตัวที่โปร่งใสสำหรับโวยวาย pro-FP
Mason Wheeler

11
+1 สำหรับ "ภาษาที่ใหม่กว่าคัดลอก C ส่วนใหญ่เป็นเพราะนักออกแบบไม่เคยเห็นอะไรเลย" ทุกครั้งที่ฉันเห็นภาษาใหม่ที่มีค่าคงที่ฐานแปดนำหน้า 0 ฉันตายภายในเล็กน้อย
Librik

4
นี่อาจเป็นประโยคแรกของการเขียนพระคัมภีร์: In the beginning, there was FORTRAN.

1
เกี่ยวกับ ADA หากตัวแปรโกลบอลถูกส่งผ่านไปยังพารามิเตอร์เข้า / ออกภาษาจะป้องกันการโทรซ้อนหรือตรวจสอบหรือแก้ไขมันหรือไม่? ถ้าไม่ฉันจะคิดว่าจะมีความแตกต่างที่ชัดเจนระหว่างพารามิเตอร์การเข้า / ออกและการอ้างอิง โดยส่วนตัวฉันต้องการเห็นภาษาสนับสนุนชุดค่าผสมทั้งหมดของ "เข้า / ออก / เข้า + ออก" และ "ตามค่า / โดยอ้างอิง / ไม่สนใจ" ดังนั้นโปรแกรมเมอร์สามารถเสนอความชัดเจนสูงสุดตามเจตนาของพวกเขา แต่เพิ่มประสิทธิภาพสูงสุด จะมีความยืดหยุ่นสูงสุดในการใช้งาน (ตราบเท่าที่มันมีเจตนารมย์โปรแกรมเมอร์)
supercat

2
@Neikos นอกจากนี้ยังมีความจริงที่ว่า0คำนำหน้าไม่สอดคล้องกับคำนำหน้าสิบทั้งหมดที่ไม่ใช่ฐาน นั่นอาจจะค่อนข้างใช้งานได้ใน C (และส่วนใหญ่เป็นภาษาที่เข้ากันได้กับแหล่งข้อมูลเช่น C ++, Objective C) หากฐานแปดเป็นทางเลือกเดียวเมื่อนำมาใช้ แต่เมื่อภาษาที่ทันสมัยกว่าใช้ทั้งสอง0xและ0ตั้งแต่เริ่มต้น คิดออก
8bittree

13

การผ่านการอ้างอิงช่วยให้มีผลข้างเคียงที่ไม่ได้ตั้งใจอย่างละเอียดซึ่งยากมากที่จะติดกับสิ่งที่เป็นไปไม่ได้ที่จะติดตามเมื่อพวกเขาเริ่มก่อให้เกิดพฤติกรรมที่ไม่ได้ตั้งใจ

ผ่านค่าโดยเฉพาะอย่างยิ่งfinal, staticหรือconstป้อนพารามิเตอร์นี้จะทำให้ทั้งชั้นของโรคจิตหายไป

ภาษาที่ไม่เปลี่ยนรูปนั้นมีการกำหนดและให้เหตุผลและเข้าใจได้ง่ายกว่าสิ่งที่เกิดขึ้นและสิ่งที่คาดว่าจะออกมาจากฟังก์ชั่น


7
มันเป็นหนึ่งในสิ่งเหล่านั้นที่คุณคิดออกหลังจากพยายามที่จะแก้ปัญหารหัส VB ​​'เก่า' ซึ่งทุกอย่างจะถูกอ้างอิงเป็นค่าเริ่มต้น
jfrankcarr

อาจเป็นเพียงว่าพื้นหลังของฉันแตกต่างกัน แต่ฉันไม่รู้เลยว่าผลข้างเคียงที่ไม่ได้ตั้งใจ "ที่ลึกซึ้งมาก ๆ ซึ่งยากมากที่จะอยู่ถัดจากสิ่งที่เป็นไปไม่ได้ที่จะติดตาม" คุณกำลังพูดถึง ฉันคุ้นเคยกับ Pascal ซึ่งค่า by-value เป็นค่าเริ่มต้น แต่สามารถใช้การอ้างอิงโดยการทำเครื่องหมายพารามิเตอร์อย่างชัดเจน (โดยทั่วไปเป็นรุ่นเดียวกันกับ C #,) และฉันไม่เคยมีปัญหามาให้ฉัน ฉันสามารถดูว่ามันจะเป็นปัญหาใน "VB โบราณ" ที่ by-ref เป็นค่าเริ่มต้น แต่เมื่อ by-ref เลือกใช้มันจะทำให้คุณคิดในขณะที่คุณเขียน
Mason Wheeler

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

1
@MasonWheeler ตัวอย่างนามแฝงง่ายๆเช่นSwapแฮ็กที่ไม่ได้ใช้ตัวแปรชั่วคราวแม้ว่าจะไม่น่าจะเป็นปัญหาด้วยตัวเอง แต่ก็แสดงให้เห็นว่าตัวอย่างที่ซับซ้อนมากขึ้นนั้นอาจยากที่จะทำการดีบัก
Mark Hurd

1
@MasonWheeler: ตัวอย่างคลาสสิกใน FORTRAN ซึ่งนำไปสู่การพูดว่า "ตัวแปรจะไม่; ค่าคงที่ไม่ได้" จะผ่านค่าคงที่จุดลอยตัวเช่น 3.0 ไปยังฟังก์ชันที่ปรับเปลี่ยนพารามิเตอร์ที่ส่งผ่าน สำหรับค่าคงที่จุดลอยตัวใด ๆ ที่ส่งผ่านไปยังฟังก์ชันระบบจะสร้างตัวแปร "ซ่อน" ที่เริ่มต้นด้วยค่าที่เหมาะสมและสามารถส่งผ่านไปยังฟังก์ชันได้ หากฟังก์ชั่นเพิ่ม 1.0 ให้กับพารามิเตอร์ของมันชุดย่อยโดยพลการของ 3.0 ในโปรแกรมก็อาจกลายเป็น 4.0
supercat

8

ทำไมหลายภาษาจึงผ่านค่า?

จุดของการแบ่งโปรแกรมขนาดใหญ่ออกเป็นรูทีนย่อยขนาดเล็กคือคุณสามารถให้เหตุผลเกี่ยวกับรูทีนย่อยได้อย่างอิสระ Pass-by-Reference แบ่งคุณสมบัตินี้ (เช่นเดียวกับสถานะที่ไม่แน่นอนที่แชร์กัน)

แม้แต่ภาษาที่คุณมีการจัดการตัวชี้อย่างชัดเจนเช่น C มันจะถูกส่งผ่านตามค่าเสมอ (คุณสามารถส่งต่อโดยการอ้างอิง แต่นั่นไม่ใช่พฤติกรรมเริ่มต้น)

ที่จริงแล้ว C อยู่เสมอโดยผ่านค่าไม่เคยผ่านการอ้างอิง คุณสามารถใช้ที่อยู่ของบางสิ่งบางอย่างและส่งผ่านที่อยู่นั้นได้ แต่ที่อยู่จะยังคงถูกส่งผ่านตามค่า

ประโยชน์ของสิ่งนี้คืออะไรทำไมมีหลายภาษาที่ผ่านค่านิยมและทำไมคนอื่นถึงผ่านอ้างอิง ?

มีสองเหตุผลหลักในการใช้การอ้างอิงแบบอ้างอิงถึง:

  1. จำลองค่าส่งคืนหลายค่า
  2. อย่างมีประสิทธิภาพ

โดยส่วนตัวฉันคิดว่า # 1 เป็นของปลอม: มันเกือบจะเป็นข้อแก้ตัวสำหรับ API ที่ไม่ดีและ / หรือการออกแบบภาษา:

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

    def foo
      # This is actually just a single return value, an array: [1, 2, 3]
      return 1, 2, 3
    end
    
    # Ruby supports destructuring bind for arrays: a, b, c = [1, 2, 3]
    one, two, three = foo
    
  3. บ่อยครั้งคุณไม่จำเป็นต้องมีค่าส่งคืนหลายค่า ตัวอย่างเช่นรูปแบบหนึ่งที่เป็นที่นิยมคือรูทีนย่อยส่งคืนรหัสข้อผิดพลาดและผลลัพธ์จริงถูกเขียนกลับโดยการอ้างอิง คุณควรส่งข้อยกเว้นหากเกิดข้อผิดพลาดที่ไม่คาดคิดหรือส่งคืนEither<Exception, T>ถ้าคาดว่าจะเกิดข้อผิดพลาด อีกรูปแบบหนึ่งคือการส่งคืนบูลีนซึ่งบอกว่าการดำเนินการประสบความสำเร็จและส่งกลับผลลัพธ์ที่แท้จริงโดยการอ้างอิง อีกครั้งหากความล้มเหลวไม่คาดคิดคุณควรโยนข้อยกเว้นแทนหากคาดว่าจะเกิดความล้มเหลวเช่นเมื่อค้นหาค่าในพจนานุกรมคุณควรส่งคืนMaybe<T>แทน

Pass-by-Reference อาจมีประสิทธิภาพมากกว่า Pass-by-value เนื่องจากคุณไม่จำเป็นต้องคัดลอกค่า

(ฉันเข้าใจว่า Haskell ผ่านการอ้างอิงถึงแม้ว่าฉันไม่แน่ใจ)

ไม่ Haskell ไม่ได้ผ่านการอ้างอิง และมันก็ไม่คุ้มค่า Pass-by-Reference และ Pass-by-value เป็นทั้งกลยุทธ์การประเมินที่เข้มงวด แต่ Haskell นั้นไม่เข้มงวด

ในความเป็นจริงข้อกำหนด Haskell ไม่ได้ระบุกลยุทธ์การประเมินเฉพาะใด ๆ การใช้งานของ Hakell ส่วนใหญ่ใช้การโทรตามชื่อและการโทรตามความต้องการ (ตัวแปรของการโทรด้วยชื่อพร้อมบันทึก) แต่มาตรฐานไม่ได้บังคับสิ่งนี้

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


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

+1 ฉันเห็นด้วยกับประเด็นนี้โดยสิ้นเชิงประเด็นของการแยกโปรแกรมขนาดใหญ่ออกเป็นรูทีนย่อยขนาดเล็กคือคุณสามารถให้เหตุผลเกี่ยวกับรูทีนย่อยได้อย่างอิสระ Pass-by-Reference แบ่งคุณสมบัตินี้ (เช่นสถานะที่ไม่แน่นอนที่แชร์กัน)
Rémi

3

มีพฤติกรรมที่แตกต่างกันขึ้นอยู่กับรุ่นการโทรของภาษาประเภทของการขัดแย้งและรุ่นหน่วยความจำของภาษา

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

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

ในกรณีของภาษาที่ใช้งานได้ฉันคิดว่าการส่งผ่านค่าหรือการอ้างอิงเป็นเพียงคำถามของการปรับให้เหมาะสม เนื่องจากฟังก์ชั่นในภาษานั้นบริสุทธิ์และปราศจากผลข้างเคียงจึงไม่มีเหตุผลที่จะคัดลอกค่ายกเว้นความเร็ว ฉันค่อนข้างมั่นใจว่าภาษาดังกล่าวมักจะแบ่งปันสำเนาของวัตถุเดียวกันด้วยค่าเดียวกันความเป็นไปได้จะใช้ได้เฉพาะเมื่อคุณใช้ semantic อ้างอิงความหมายผ่าน Python ใช้เคล็ดลับนี้สำหรับสตริงจำนวนเต็มและสามัญ (เช่นเมธอดและชื่อคลาส) อธิบายว่าทำไมจำนวนเต็มและสตริงจึงเป็นวัตถุคงที่ใน Python สิ่งนี้ยังช่วยในการปรับรหัสให้เหมาะสมอีกครั้งโดยอนุญาตให้มีตัวชี้ตัวอย่าง comparaison แทน comparaison ของเนื้อหาและทำการประเมินข้อมูลภายในบางอย่างที่ขี้เกียจ


2

คุณสามารถผ่านการแสดงออกโดยค่ามันเป็นธรรมชาติ การส่งผ่านนิพจน์โดยการอ้างอิง (ชั่วคราว) คือ ... แปลก


1
นอกจากนี้การส่งผ่านนิพจน์โดยการอ้างอิงชั่วคราวสามารถทำให้เกิดข้อบกพร่องที่ไม่ดี (ในภาษา stateful) เมื่อคุณเปลี่ยนมันอย่างมีความสุข (เนื่องจากเป็นเพียงชั่วคราว) แต่จากนั้นมันจะย้อนกลับเมื่อคุณผ่านตัวแปรและคุณต้องคิดวิธีแก้ปัญหาที่น่าเกลียด +0 แทน foo
herby

2

หากคุณผ่านการอ้างอิงแสดงว่าคุณทำงานอย่างมีประสิทธิภาพกับค่านิยมทั่วโลกพร้อมกับปัญหาทั้งหมดของ globals (เช่นขอบเขตและผลข้างเคียงที่ไม่ได้ตั้งใจ)

บางครั้งการอ้างอิงก็มีประโยชน์เช่นกัน แต่มันไม่ควรเป็นตัวเลือกแรกของคุณ


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