อะไรคือความแตกต่างระหว่าง
- พารามิเตอร์ที่ส่งผ่านโดยการอ้างอิง
- พารามิเตอร์ผ่านค่าหรือไม่
กรุณายกตัวอย่างให้ฉันหน่อยได้ไหม
อะไรคือความแตกต่างระหว่าง
กรุณายกตัวอย่างให้ฉันหน่อยได้ไหม
คำตอบ:
แรกและสำคัญที่สุด"การผ่านค่าเมื่อเทียบกับการส่งผ่านโดยการอ้างอิง" ความแตกต่างตามที่กำหนดไว้ในทฤษฎี CS อยู่ในขณะนี้ล้าสมัยเพราะเทคนิคที่กำหนดไว้เดิมเป็น "ผ่านการอ้างอิง" ได้ลดลงตั้งแต่ออกจากความโปรดปรานและไม่ค่อยมีใครใช้ในขณะนี้ 1
ภาษาที่ใหม่กว่า2มีแนวโน้มที่จะใช้เทคนิคที่ต่างกัน (แต่คล้ายกัน) เพื่อให้ได้เอฟเฟกต์แบบเดียวกัน (ดูด้านล่าง) ซึ่งเป็นแหล่งที่มาหลักของความสับสน
แหล่งที่มาของความสับสนที่สองคือข้อเท็จจริงที่ว่า"การส่งต่อโดยอ้างอิง", "อ้างอิง" มีความหมายแคบกว่าคำว่า "อ้างอิง" ทั่วไป (เนื่องจากวลีมีมาก่อน)
ตอนนี้นิยามที่แท้จริงคือ:
เมื่อพารามิเตอร์ถูกส่งผ่านโดยการอ้างอิงผู้เรียกและผู้รับใช้จะใช้ตัวแปรเดียวกันสำหรับพารามิเตอร์ หากผู้รับการแก้ไขพารามิเตอร์ตัวแปรผลกระทบที่สามารถมองเห็นได้กับตัวแปรของผู้โทร
เมื่อพารามิเตอร์ถูกส่งผ่านโดยค่าผู้เรียกและผู้รับมีสองตัวแปรอิสระที่มีค่าเดียวกัน หากผู้ติดตามปรับเปลี่ยนตัวแปรพารามิเตอร์จะไม่เห็นเอฟเฟกต์ผู้โทร
สิ่งที่ควรทราบในคำจำกัดความนี้คือ:
"Variable" ที่นี่หมายถึงตัวแปรของผู้โทร (ท้องถิ่นหรือทั่วโลก) - นั่นคือถ้าฉันส่งตัวแปรท้องถิ่นโดยการอ้างอิงและกำหนดให้กับมันฉันจะเปลี่ยนตัวแปรของผู้โทรได้เองเช่นไม่ใช่สิ่งที่ชี้ไปหากเป็นตัวชี้ .
ความหมายของ "การอ้างอิง" ใน "ผ่านการอ้างอิง" ความแตกต่างกับคำว่า "อ้างอิง" ทั่วไปคือคำว่า"อ้างอิง" นี้เป็นการชั่วคราวและโดยนัย สิ่งที่ผู้ได้รับโดยทั่วไปคือ"ตัวแปร" ที่เป็น "เหมือนกัน" กับต้นฉบับ ความสำเร็จของเอฟเฟ็กต์นี้โดยเฉพาะนั้นมีความเกี่ยวข้องเพียงใด (เช่นภาษาอาจเปิดเผยรายละเอียดการใช้งานบางอย่าง - ที่อยู่พอยน์เตอร์การอ้างอิง - นี่คือสิ่งที่ไม่เกี่ยวข้องทั้งหมดหากผลกระทบสุทธิคือสิ่งนี้
ตอนนี้ในภาษาสมัยใหม่ตัวแปรมีแนวโน้มที่จะเป็น "ประเภทอ้างอิง" (แนวคิดอื่นที่คิดค้นภายหลัง "ส่งต่อโดยอ้างอิง" และได้รับแรงบันดาลใจจากมัน) นั่นคือข้อมูลวัตถุจริงจะถูกจัดเก็บแยกจากที่อื่น (ปกติบนกอง) และ มีเพียง "การอ้างอิง" ที่มีอยู่ในตัวแปรและส่งผ่านเป็นพารามิเตอร์ 3
การส่งการอ้างอิงดังกล่าวตกอยู่ภายใต้การส่งผ่านตามค่าเนื่องจากค่าของตัวแปรเป็นเทคนิคการอ้างอิงเองไม่ใช่วัตถุที่อ้างอิง อย่างไรก็ตามผลกระทบสุทธิในโปรแกรมสามารถเหมือนกับค่า pass-by-value หรือ pass-by-reference:
อย่างที่คุณอาจเห็นเทคนิคของคู่นี้เกือบจะเหมือนกันกับในคำจำกัดความเฉพาะกับระดับของการเปลี่ยนทิศทาง: เพียงแค่แทนที่ "ตัวแปร" ด้วย "วัตถุอ้างอิง"
ไม่มีชื่อที่ตกลงกันไว้สำหรับพวกเขาซึ่งนำไปสู่คำอธิบายที่บิดเบี้ยวเช่น "การเรียกตามค่าที่ค่าเป็นการอ้างอิง" ในปี 1975 บาร์บาร่าลิสคอฟแนะนำคำว่า "การโทรหาโดยการแชร์วัตถุ " (หรือบางครั้งก็เป็นเพียงแค่ ยิ่งกว่านั้นวลีเหล่านี้ไม่ได้วาดคู่ขนานกับคู่ดั้งเดิม ไม่น่าแปลกใจที่คำศัพท์เก่า ๆ ถูกนำกลับมาใช้ใหม่หากไม่มีสิ่งใดที่ดีกว่านำไปสู่ความสับสน 4
หมายเหตุ : เป็นเวลานานคำตอบนี้เคยพูดว่า:
สมมติว่าฉันต้องการแบ่งปันหน้าเว็บกับคุณ ถ้าฉันบอก URL ฉันกำลังผ่านการอ้างอิง คุณสามารถใช้ URL นั้นเพื่อดูหน้าเว็บเดียวกับที่ฉันเห็น หากหน้านั้นมีการเปลี่ยนแปลงเราทั้งคู่จะเห็นการเปลี่ยนแปลง หากคุณลบ URL สิ่งที่คุณทำอยู่นั้นกำลังทำลายการอ้างอิงของคุณไปยังหน้านั้น - คุณไม่ได้ลบหน้าจริงออกไป
ถ้าฉันพิมพ์หน้านี้และให้ผลงานพิมพ์ออกมาฉันก็ผ่านคุณค่าไป หน้าของคุณเป็นสำเนาดั้งเดิมที่ไม่ได้เชื่อมต่อ คุณจะไม่เห็นการเปลี่ยนแปลงใด ๆ ที่ตามมาและการเปลี่ยนแปลงใด ๆ ที่คุณทำ (เช่นการขีดเขียนในงานพิมพ์ของคุณ) จะไม่ปรากฏในหน้าต้นฉบับ หากคุณทำลายงานพิมพ์คุณได้ทำลายสำเนาของวัตถุจริงๆ - แต่หน้าเว็บดั้งเดิมยังคงไม่เปลี่ยนแปลง
สิ่งนี้ถูกต้องส่วนใหญ่ยกเว้นความหมายที่แคบกว่าของ "การอ้างอิง" - มันเป็นทั้งชั่วคราวและโดยปริยาย (ไม่จำเป็นต้องมี แต่ชัดเจนและ / หรือถาวรเป็นคุณลักษณะเพิ่มเติมไม่ใช่ส่วนหนึ่งของความหมายแบบส่งต่อโดยอ้างอิง ตามที่อธิบายข้างต้น) การเปรียบเทียบที่ใกล้กว่าจะให้สำเนาเอกสารกับคุณและเชิญชวนให้คุณทำงานกับต้นฉบับ
1 ยกเว้นว่าคุณกำลังเขียนโปรแกรมใน Fortran หรือ Visual Basic ไม่ใช่พฤติกรรมเริ่มต้นและในภาษาส่วนใหญ่ที่ใช้งานในปัจจุบันการโทรโดยอ้างอิงจริงไม่สามารถทำได้
2 จำนวนที่มากกว่าของคนที่มีอายุมากกว่าสนับสนุนเช่นกัน
3 ในหลายภาษาที่ทันสมัยทุกประเภทเป็นประเภทอ้างอิง วิธีการนี้ได้รับการริเริ่มโดย CLU ภาษาในปี 1975 และได้รับการรับรองจากภาษาอื่น ๆ มากมายรวมถึง Python และ Ruby และภาษาอื่น ๆ อีกมากมายใช้วิธีไฮบริดโดยที่บางประเภทคือ "ประเภทค่า" และอื่น ๆ คือ "ประเภทอ้างอิง" - ในบรรดาภาษาเหล่านั้น ได้แก่ C #, Java และ JavaScript
4 มีอะไรที่ไม่ดีคือมีการรีไซเคิลเป็นคำเก่าที่เหมาะสมต่อ seแต่หนึ่งอย่างใดให้ชัดเจนซึ่งหมายถูกนำมาใช้ในแต่ละครั้ง การไม่ทำเช่นนั้นเป็นสิ่งที่ทำให้เกิดความสับสน
มันเป็นวิธีการส่งผ่านข้อโต้แย้งไปยังฟังก์ชั่น การส่งต่อโดยการอ้างอิงหมายถึงพารามิเตอร์ที่เรียกว่าฟังก์ชั่น 'จะเหมือนกับอาร์กิวเมนต์ที่ส่งผ่านของผู้โทร (ไม่ใช่ค่า แต่เป็นข้อมูลเฉพาะตัว - ตัวแปรนั้น) Pass by value หมายถึงพารามิเตอร์ที่เรียกว่าฟังก์ชั่น 'จะเป็นสำเนาของอาร์กิวเมนต์ที่ส่งผ่านของผู้โทร ค่าจะเหมือนกัน แต่ข้อมูลเฉพาะตัว - ตัวแปร - นั้นแตกต่างกัน ดังนั้นการเปลี่ยนแปลงพารามิเตอร์ที่ทำโดยฟังก์ชั่นที่เรียกว่าในกรณีหนึ่งการเปลี่ยนแปลงอาร์กิวเมนต์ผ่านและในกรณีอื่นเพียงแค่เปลี่ยนค่าของพารามิเตอร์ในฟังก์ชั่นที่เรียกว่า (ซึ่งเป็นเพียงการคัดลอก) รีบด่วน:
ref
ใช้ที่ caller และ function ที่เรียก) จอนสกีตนอกจากนี้ยังมีคำอธิบายที่ดีของนี้ที่นี่รหัส
เนื่องจากภาษาของฉันคือ C ++ ฉันจะใช้ที่นี่
// passes a pointer (called reference in java) to an integer
void call_by_value(int *p) { // :1
p = NULL;
}
// passes an integer
void call_by_value(int p) { // :2
p = 42;
}
// passes an integer by reference
void call_by_reference(int & p) { // :3
p = 42;
}
// this is the java style of passing references. NULL is called "null" there.
void call_by_value_special(int *p) { // :4
*p = 10; // changes what p points to ("what p references" in java)
// only changes the value of the parameter, but *not* of
// the argument passed by the caller. thus it's pass-by-value:
p = NULL;
}
int main() {
int value = 10;
int * pointer = &value;
call_by_value(pointer); // :1
assert(pointer == &value); // pointer was copied
call_by_value(value); // :2
assert(value == 10); // value was copied
call_by_reference(value); // :3
assert(value == 42); // value was passed by reference
call_by_value_special(pointer); // :4
// pointer was copied but what pointer references was changed.
assert(value == 10 && pointer == &value);
}
และตัวอย่างใน Java จะไม่เจ็บ:
class Example {
int value = 0;
// similar to :4 case in the c++ example
static void accept_reference(Example e) { // :1
e.value++; // will change the referenced object
e = null; // will only change the parameter
}
// similar to the :2 case in the c++ example
static void accept_primitive(int v) { // :2
v++; // will only change the parameter
}
public static void main(String... args) {
int value = 0;
Example ref = new Example(); // reference
// note what we pass is the reference, not the object. we can't
// pass objects. The reference is copied (pass-by-value).
accept_reference(ref); // :1
assert ref != null && ref.value == 1;
// the primitive int variable is copied
accept_primitive(value); // :2
assert value == 0;
}
}
วิกิพีเดีย
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_value
http://en.wikipedia.org/wiki/Pass_by_reference#Call_by_reference
ผู้ชายคนนี้สวยมากเล็บมัน:
คำตอบจำนวนมากที่นี่ (และโดยเฉพาะคำตอบที่ upvoted มากที่สุด) นั้นไม่ถูกต้องเนื่องจากพวกเขาเข้าใจผิดว่า "การโทรโดยการอ้างอิง" หมายถึงอะไรจริงๆ นี่คือความพยายามของฉันที่จะตั้งเรื่องตรง
ในแง่ง่ายที่สุด:
ในแง่คำเปรียบเทียบ:
โปรดทราบว่าแนวคิดทั้งสองนี้เป็นอิสระอย่างสมบูรณ์และ orthogonal จากแนวคิดของประเภทการอ้างอิง (ซึ่งใน Java เป็นทุกประเภทที่เป็นชนิดย่อยของObject
และใน C # ทุกclass
ประเภท) หรือแนวคิดของประเภทตัวชี้เช่นใน C (ซึ่งเทียบเท่ากับความหมาย ถึง "ประเภทอ้างอิง" ของ Java เพียง แต่มีไวยากรณ์ที่แตกต่างกัน)
แนวคิดของประเภทการอ้างอิงสอดคล้องกับ URL: มันเป็นทั้งข้อมูลตัวหนึ่งและเป็นข้อมูลอ้างอิง ( ตัวชี้ถ้าคุณต้องการ) กับข้อมูลอื่น คุณสามารถมีสำเนา URL จำนวนมากในที่ต่าง ๆ และพวกเขาจะไม่เปลี่ยนแปลงสิ่งที่เว็บไซต์ทั้งหมดเชื่อมโยงไปถึง หากเว็บไซต์นั้นได้รับการปรับปรุงการคัดลอก URL ทุกครั้งจะยังคงนำไปสู่ข้อมูลที่อัปเดต ในทางกลับกันการเปลี่ยน URL ในที่เดียวจะไม่มีผลกับสำเนาที่เป็นลายลักษณ์อักษรอื่น ๆ ของ URL
โปรดทราบว่า C ++ มีแนวคิดเกี่ยวกับ "การอ้างอิง" (เช่นint&
) ที่ไม่เหมือนกับ Java และ C # 's "ประเภทการอ้างอิง" แต่ก็เหมือนกับ "การเรียกโดยการอ้างอิง" "ประเภทอ้างอิง" ของ Java และ C # และทุกประเภทใน Python เหมือนกับ C และ C ++ เรียกว่า "ประเภทตัวชี้" (เช่นint*
)
ตกลงนี่คือคำอธิบายที่ยาวและเป็นทางการมากขึ้น
เริ่มต้นด้วยฉันต้องการเน้นบางส่วนของคำศัพท์ที่สำคัญเพื่อช่วยชี้แจงคำตอบของฉันและเพื่อให้แน่ใจว่าเราทุกคนอ้างถึงแนวคิดเดียวกันเมื่อเราใช้คำ (ในทางปฏิบัติฉันเชื่อว่าความสับสนส่วนใหญ่เกี่ยวกับหัวข้อต่าง ๆ เช่นเกิดจากการใช้คำในวิธีที่ไม่สื่อสารความหมายที่ตั้งใจไว้อย่างสมบูรณ์)
ในการเริ่มต้นนี่คือตัวอย่างในภาษา C บางอย่างของการประกาศฟังก์ชัน:
void foo(int param) { // line 1
param += 1;
}
และนี่คือตัวอย่างของการเรียกใช้ฟังก์ชันนี้:
void bar() {
int arg = 1; // line 2
foo(arg); // line 3
}
ใช้ตัวอย่างนี้ฉันต้องการกำหนดบิตที่สำคัญบางคำศัพท์:
foo
เป็นฟังก์ชั่นที่ประกาศในบรรทัดที่ 1 (Java ยืนยันในการสร้างวิธีการฟังก์ชั่นทั้งหมด แต่แนวคิดจะเหมือนกันโดยไม่สูญเสียความนิยมทั่วไป C และ C ++ สร้างความแตกต่างระหว่างการประกาศและคำจำกัดความที่ฉันจะไม่เข้าไปที่นี่)param
เป็นพารามิเตอร์อย่างเป็นทางการไปfoo
ยังประกาศในบรรทัดที่ 1arg
เป็นตัวแปรโดยเฉพาะอย่างยิ่งตัวแปรท้องถิ่นของฟังก์ชั่นbar
ประกาศและเริ่มต้นในบรรทัดที่ 2arg
เป็นอาร์กิวเมนต์สำหรับการเรียกใช้เฉพาะของfoo
on line 3มีแนวคิดที่สำคัญสองชุดที่แยกความแตกต่างได้ที่นี่ สิ่งแรกคือค่าเมื่อเทียบกับตัวแปร :
bar
ฟังก์ชั่นข้างต้นหลังจากบรรทัดint arg = 1;
, การแสดงออกarg
ที่มีค่า 1
final
หรือ C # readonly
) หรือเปลี่ยนแปลงไม่ได้อย่างลึกซึ้ง (เช่นใช้ C ++ const
)แนวคิดที่สำคัญอีกคู่ที่แยกความแตกต่างคือพารามิเตอร์เปรียบเทียบกับอาร์กิวเมนต์ :
ในการโทรตามค่าพารามิเตอร์ที่เป็นทางการของฟังก์ชันคือตัวแปรที่สร้างขึ้นใหม่สำหรับการเรียกใช้ฟังก์ชันและจะเริ่มต้นด้วยค่าของอาร์กิวเมนต์
สิ่งนี้ทำงานในลักษณะเดียวกับที่ตัวแปรชนิดอื่นใดจะเริ่มต้นด้วยค่า ตัวอย่างเช่น:
int arg = 1;
int another_variable = arg;
ที่นี่arg
และanother_variable
เป็นตัวแปรอิสระอย่างสมบูรณ์ - ค่าของพวกเขาสามารถเปลี่ยนเป็นอิสระจากกัน อย่างไรก็ตามในจุดที่another_variable
มีการประกาศก็จะเริ่มต้นที่จะเก็บค่าเดียวกันกับที่arg
ถือ - 1
ซึ่งเป็น
เนื่องจากเป็นตัวแปรอิสระการเปลี่ยนแปลงจึงanother_variable
ไม่ส่งผลกระทบarg
:
int arg = 1;
int another_variable = arg;
another_variable = 2;
assert arg == 1; // true
assert another_variable == 2; // true
ตรงนี้เป็นความสัมพันธ์ระหว่างarg
และparam
ในตัวอย่างของเราด้านบนซึ่งฉันจะทำซ้ำที่นี่เพื่อสมมาตร:
void foo(int param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
มันเหมือนกับว่าเราได้เขียนโค้ดด้วยวิธีนี้:
// entering function "bar" here
int arg = 1;
// entering function "foo" here
int param = arg;
param += 1;
// exiting function "foo" here
// exiting function "bar" here
นั่นคือลักษณะการกำหนดของสิ่งที่เรียกโดยค่าหมายถึงว่า callee ( foo
ในกรณีนี้) ได้รับค่าเป็นข้อโต้แย้ง แต่มีตัวแปรแยกต่างหากสำหรับค่าเหล่านั้นจากตัวแปรของผู้โทร ( bar
ในกรณีนี้)
กลับไปที่คำอุปมาของฉันข้างต้นถ้าฉันbar
และคุณfoo
เมื่อฉันโทรหาคุณฉันจะส่งกระดาษแผ่นหนึ่งให้คุณพร้อมค่าที่เขียน คุณเรียกกระดาษชิ้นparam
นั้น ค่าที่เป็นสำเนาของค่าที่ผมได้เขียนไว้ในสมุดบันทึกของฉัน (ตัวแปรท้องถิ่นของฉัน) arg
ในสายผมตัวแปร
(นอกเหนือไปจาก: ขึ้นอยู่กับฮาร์ดแวร์และระบบปฏิบัติการมีการเรียกการประชุมที่หลากหลายเกี่ยวกับวิธีที่คุณเรียกฟังก์ชั่นหนึ่งจากอีกการประชุมการโทรเป็นเหมือนเราตัดสินใจว่าฉันจะเขียนค่าลงบนกระดาษของฉันแล้วส่งให้คุณ หรือถ้าคุณมีกระดาษแผ่นหนึ่งที่ฉันเขียนไว้หรือถ้าฉันเขียนมันไว้บนกำแพงตรงหน้าเราทั้งคู่นี่เป็นเรื่องที่น่าสนใจเช่นกัน แต่ไกลเกินกว่าขอบเขตของคำตอบที่ยาวนานนี้)
ในการโทรโดยการอ้างอิงพารามิเตอร์อย่างเป็นทางการของฟังก์ชั่นเป็นเพียงชื่อใหม่สำหรับตัวแปรเดียวกับที่ผู้โทรเข้ามาเป็นข้อโต้แย้ง
กลับไปที่ตัวอย่างของเราด้านบนมันเทียบเท่ากับ:
// entering function "bar" here
int arg = 1;
// entering function "foo" here
// aha! I note that "param" is just another name for "arg"
arg /* param */ += 1;
// exiting function "foo" here
// exiting function "bar" here
เนื่องจากparam
เป็นเพียงอีกชื่อหนึ่งarg
- นั่นคือพวกเขาเป็นตัวแปรเดียวกันการเปลี่ยนแปลงจะถูกแสดงในparam
arg
นี่เป็นวิธีพื้นฐานที่การโทรโดยการอ้างอิงแตกต่างจากการโทรตามค่า
มีภาษาน้อยมากที่สนับสนุนการอ้างอิง แต่ C ++ สามารถทำสิ่งนี้ได้:
void foo(int& param) {
param += 1;
}
void bar() {
int arg = 1;
foo(arg);
}
ในกรณีparam
นี้ไม่เพียง แต่มีค่าเดียวกับที่arg
จริงมันคือ arg
(โดยชื่ออื่น) และbar
สามารถสังเกตarg
ได้ว่ามันเพิ่มขึ้น
โปรดทราบว่านี่ไม่ใช่วิธีการของ Java, JavaScript, C, Objective-C, Python หรือภาษายอดนิยมอื่น ๆ ในปัจจุบัน ซึ่งหมายความว่าภาษาเหล่านั้นไม่ได้ถูกเรียกโดยการอ้างอิงพวกเขาจะโทรตามค่า
หากสิ่งที่คุณมีคือการเรียกตามค่าแต่ค่าจริงเป็นประเภทการอ้างอิงหรือประเภทตัวชี้แล้ว "ค่า" ตัวเองไม่น่าสนใจมาก (เช่นใน C เป็นเพียงจำนวนเต็มขนาดเฉพาะแพลตฟอร์ม) - สิ่งที่ ที่น่าสนใจคืออะไรที่คุ้มค่าชี้ไปที่
หากประเภทการอ้างอิง (นั่นคือตัวชี้) ชี้ไปที่ไม่แน่นอนแล้วผลที่น่าสนใจเป็นไปได้: คุณสามารถปรับเปลี่ยนค่าชี้ไปที่และผู้โทรสามารถสังเกตเห็นการเปลี่ยนแปลงค่าชี้ไปที่แม้ว่าผู้โทรไม่สามารถสังเกตเห็น เปลี่ยนเป็นตัวชี้
ในการขอยืม URL ที่คล้ายคลึงกันอีกครั้งข้อเท็จจริงที่ว่าฉันให้คุณคัดลอก URL ไปยังเว็บไซต์นั้นไม่น่าสนใจเป็นพิเศษหากสิ่งที่เราทั้งคู่ใส่ใจคือเว็บไซต์ไม่ใช่ URL ความจริงที่ว่าคุณเขียนหวัดคัดลอก URL ของคุณไม่มีผลกับสำเนา URL ของฉันไม่ใช่สิ่งที่เราใส่ใจ (และอันที่จริงแล้วในภาษาเช่น Java และ Python นั้น "URL" หรือค่าประเภทอ้างอิงสามารถ ไม่สามารถแก้ไขได้เลยสิ่งเดียวเท่านั้นที่ชี้ได้)
Barbara Liskov เมื่อเธอคิดค้นภาษาการเขียนโปรแกรม CLU (ซึ่งมีความหมายเหล่านี้) ได้ตระหนักว่าคำที่มีอยู่ "การเรียกตามค่า" และ "การโทรโดยการอ้างอิง" ไม่มีประโยชน์อย่างยิ่งสำหรับการอธิบายความหมายของภาษาใหม่นี้ ดังนั้นเธอจึงคิดค้นคำใหม่: โทรโดยใช้งานร่วมกันของวัตถุ
เมื่อพูดถึงภาษาที่มีการเรียกตามค่าทางเทคนิค แต่ที่ประเภทการใช้งานทั่วไปคือการอ้างอิงหรือประเภทตัวชี้ (นั่นคือเกือบทุกความจำเป็นที่ทันสมัยภาษาการเขียนโปรแกรมเชิงวัตถุหรือหลายกระบวนทัศน์) ฉันพบว่ามันสับสนน้อยกว่า เพียงแค่หลีกเลี่ยงการพูดคุยเกี่ยวกับการเรียกร้องโดยค่าหรือโทรโดยการอ้างอิง ติดเพื่อโทรด้วยการแบ่งปันวัตถุ (หรือเพียงแค่โทรตามวัตถุ ) และจะไม่มีใครสับสน :-)
The first is value versus variable.
The other important pair of concepts to distinguish is parameter versus argument:
ก่อนที่จะเข้าใจคำศัพท์ 2 ข้อคุณต้องเข้าใจสิ่งต่อไปนี้ วัตถุทุกชิ้นมี 2 สิ่งที่สามารถทำให้แตกต่าง
ดังนั้นถ้าคุณพูด employee.name = "John"
รู้ว่ามี 2 name
สิ่งที่เกี่ยวกับ ค่าของมันซึ่งเป็นและยังเป็นที่ตั้งของมันในหน่วยความจำซึ่งเป็นเลขฐานสิบหกบางอาจจะเช่นนี้"John"
0x7fd5d258dd00
ขึ้นอยู่กับสถาปัตยกรรมของภาษาหรือชนิด (คลาส, โครงสร้าง, ฯลฯ ) ของวัตถุของคุณคุณอาจกำลังถ่ายโอน"John"
หรือ0x7fd5d258dd00
การผ่าน"John"
เป็นที่รู้จักกันว่าผ่านค่า การผ่าน0x7fd5d258dd00
เป็นที่รู้จักกันว่าผ่านโดยการอ้างอิง "John"
ทุกคนที่จะชี้ไปที่สถานที่ตั้งของหน่วยความจำนี้จะมีการเข้าถึงค่าของ
สำหรับข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนี้ฉันขอแนะนำให้คุณอ่านเกี่ยวกับการยกเลิกการอ้างอิงตัวชี้และทำไมต้องเลือก struct (ประเภทค่า) บนคลาส (ประเภทอ้างอิง)
นี่คือตัวอย่าง:
#include <iostream>
void by_val(int arg) { arg += 2; }
void by_ref(int&arg) { arg += 2; }
int main()
{
int x = 0;
by_val(x); std::cout << x << std::endl; // prints 0
by_ref(x); std::cout << x << std::endl; // prints 2
int y = 0;
by_ref(y); std::cout << y << std::endl; // prints 2
by_val(y); std::cout << y << std::endl; // prints 2
}
y
ถูกตั้งค่าเป็น 2 โดยบรรทัดก่อนหน้าแล้ว ทำไมมันกลับเป็น 0
วิธีที่ง่ายที่สุดในการรับไฟล์นี้คือไฟล์ Excel สมมุติว่าคุณมีตัวเลขสองตัวคือ 5 และ 2 ในเซลล์ A1 และ B1 และคุณต้องการหาผลรวมของพวกมันในเซลล์ที่สามสมมุติว่า A2 คุณสามารถทำได้สองวิธี
โดยการส่งค่าไปยังเซลล์ A2โดยพิมพ์= 5 + 2ลงในเซลล์นี้ ในกรณีนี้หากค่าของเซลล์ A1 หรือ B1 เปลี่ยนไปผลรวมใน A2 จะยังคงเหมือนเดิม
หรือโดยการผ่าน“อ้างอิง” ของเซลล์ A1 และ B1 เซลล์ A2โดยพิมพ์= A1 + B1 ในกรณีนี้หากค่าของเซลล์ A1 หรือ B1 เปลี่ยนไปผลรวมใน A2 ก็จะเปลี่ยนเช่นกัน
เมื่อผ่านโดยอ้างอิงคุณจะผ่านตัวชี้ไปที่ตัวแปร ผ่านค่าที่คุณกำลังผ่านสำเนาของตัวแปร ในการใช้งานขั้นพื้นฐานนี้หมายถึงการเปลี่ยนผ่านโดยอ้างอิงกับตัวแปรที่จะเห็นเป็นวิธีการโทรและผ่านค่าที่พวกเขาเคยชิน
ผ่านค่าส่งสำเนาของข้อมูลที่เก็บไว้ในตัวแปรที่คุณระบุผ่านการอ้างอิงส่งลิงค์โดยตรงไปยังตัวแปรเอง ดังนั้นถ้าคุณส่งตัวแปรโดยการอ้างอิงแล้วเปลี่ยนตัวแปรภายในบล็อกที่คุณส่งเข้าไปตัวแปรเดิมจะถูกเปลี่ยน หากคุณผ่านค่าตัวแปรเดิมจะไม่สามารถเปลี่ยนได้โดยบล็อกที่คุณส่งเข้าไป แต่คุณจะได้รับสำเนาของสิ่งที่มีอยู่ในช่วงเวลาของการโทร
ผ่านค่า - ฟังก์ชั่นคัดลอกตัวแปรและทำงานกับสำเนา (ดังนั้นจึงไม่เปลี่ยนแปลงอะไรในตัวแปรดั้งเดิม)
ผ่านการอ้างอิง - ฟังก์ชั่นใช้ตัวแปรเดิมถ้าคุณเปลี่ยนตัวแปรในฟังก์ชั่นอื่น ๆ ก็จะเปลี่ยนตัวแปรเดิมเช่นกัน
ตัวอย่าง (คัดลอกและใช้ / ลองด้วยตัวคุณเองและดู):
#include <iostream>
using namespace std;
void funct1(int a){ //pass-by-value
a = 6; //now "a" is 6 only in funct1, but not in main or anywhere else
}
void funct2(int &a){ //pass-by-reference
a = 7; //now "a" is 7 both in funct2, main and everywhere else it'll be used
}
int main()
{
int a = 5;
funct1(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 5
funct2(a);
cout<<endl<<"A is currently "<<a<<endl<<endl; //will output 7
return 0;
}
ทำให้ง่าย peeps กำแพงข้อความอาจเป็นนิสัยที่ไม่ดี
ความแตกต่างที่สำคัญระหว่างพวกเขาคือตัวแปรประเภทค่าเก็บค่าดังนั้นการระบุตัวแปรประเภทค่าในการเรียกวิธีการส่งสำเนาของค่าของตัวแปรนั้นไปยังวิธีการ ตัวแปรประเภทการอ้างอิงเก็บการอ้างอิงไปยังวัตถุดังนั้นการระบุตัวแปรประเภทการอ้างอิงเป็นอาร์กิวเมนต์ผ่านวิธีการคัดลอกของการอ้างอิงจริงที่อ้างอิงถึงวัตถุ แม้ว่าการอ้างอิงจะถูกส่งผ่านตามค่าวิธีการนั้นยังสามารถใช้การอ้างอิงที่ได้รับเพื่อโต้ตอบกับ - และอาจแก้ไขได้ - วัตถุดั้งเดิม ในทำนองเดียวกันเมื่อส่งคืนข้อมูลจากวิธีการผ่านคำสั่งส่งคืนวิธีการส่งคืนสำเนาของค่าที่เก็บไว้ในตัวแปรประเภทค่าหรือสำเนาของการอ้างอิงที่เก็บไว้ในตัวแปรประเภทการอ้างอิง เมื่อมีการส่งคืนการอ้างอิงวิธีการโทรสามารถใช้การอ้างอิงนั้นเพื่อโต้ตอบกับวัตถุที่ถูกอ้างอิง ดังนั้น,
ใน c # การส่งผ่านตัวแปรโดยการอ้างอิงเพื่อให้วิธีการที่เรียกว่าสามารถแก้ไขตัวแปร C # ให้คำหลักอ้างอิงและออก การใช้คำหลักอ้างอิงกับการประกาศพารามิเตอร์ช่วยให้คุณสามารถส่งตัวแปรไปยังวิธีการโดยอ้างอิง - วิธีการที่เรียกว่าจะสามารถแก้ไขตัวแปรเดิมในการโทร คีย์เวิร์ด ref ใช้สำหรับตัวแปรที่ถูกกำหนดค่าเริ่มต้นในวิธีการโทรแล้ว โดยปกติเมื่อการเรียกเมธอดมีตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นเป็นอาร์กิวเมนต์คอมไพเลอร์จะสร้างข้อผิดพลาด การนำหน้าพารามิเตอร์ด้วยคีย์เวิร์ด out สร้างพารามิเตอร์เอาต์พุต สิ่งนี้บ่งชี้ให้คอมไพเลอร์ว่าอาร์กิวเมนต์จะถูกส่งผ่านไปยังวิธีการที่เรียกว่าโดยการอ้างอิงและวิธีการที่เรียกว่าจะกำหนดค่าให้กับตัวแปรเดิมในการโทร หากวิธีนี้ไม่ได้กำหนดค่าให้กับพารามิเตอร์เอาต์พุตในทุกเส้นทางที่เป็นไปได้ของการดำเนินการคอมไพเลอร์จะสร้างข้อผิดพลาด สิ่งนี้ยังป้องกันไม่ให้คอมไพเลอร์สร้างข้อความแสดงข้อผิดพลาดสำหรับตัวแปรที่ไม่ได้กำหนดค่าเริ่มต้นที่ถูกส่งผ่านเป็นอาร์กิวเมนต์ไปยังเมธอด เมธอดสามารถส่งคืนค่าเดียวให้แก่ผู้เรียกผ่านทางคำสั่ง return แต่สามารถส่งคืนค่าจำนวนมากได้โดยระบุพารามิเตอร์เอาต์พุตหลายตัว (ref และ / หรือ out)
ดูการสนทนา c # และตัวอย่างที่นี่ลิงค์ข้อความ
ตัวอย่าง:
class Dog
{
public:
barkAt( const std::string& pOtherDog ); // const reference
barkAt( std::string pOtherDog ); // value
};
const &
โดยทั่วไปจะดีที่สุด คุณไม่ต้องเสียค่าก่อสร้างและค่าปรับจากการทำลายล้าง หากการอ้างอิงไม่เชื่อมต่ออินเทอร์เฟซของคุณกำลังแนะนำว่ามันจะเปลี่ยนการส่งผ่านข้อมูล
ในระยะสั้นผ่านตามมูลค่าคือสิ่งที่มันเป็นและผ่านการอ้างอิงคือที่มันเป็น
หากค่าของคุณคือ VAR1 = "Happy Guy!" คุณจะเห็น "Happy Guy!" เท่านั้น หาก VAR1 เปลี่ยนเป็น "Happy Gal!" คุณจะไม่รู้ตัว ถ้ามันผ่านการอ้างอิงและการเปลี่ยนแปลง VAR1 คุณจะ
หากคุณไม่ต้องการเปลี่ยนค่าของตัวแปรดั้งเดิมหลังจากส่งผ่านไปยังฟังก์ชันฟังก์ชันควรสร้างด้วยพารามิเตอร์" pass by value "
จากนั้นฟังก์ชันจะมีเฉพาะค่า แต่ไม่ใช่ที่อยู่ของตัวแปรที่ส่งผ่าน หากไม่มีที่อยู่ของตัวแปรรหัสภายในฟังก์ชั่นจะไม่สามารถเปลี่ยนค่าตัวแปรตามที่เห็นจากด้านนอกของฟังก์ชัน
แต่ถ้าคุณต้องการที่จะให้ฟังก์ชั่นความสามารถในการเปลี่ยนค่าของตัวแปรเท่าที่เห็นจากภายนอกที่คุณจำเป็นต้องใช้ผ่านการอ้างอิง เนื่องจากทั้งค่าและที่อยู่ (การอ้างอิง) ถูกส่งผ่านและพร้อมใช้งานภายในฟังก์ชัน
pass by value หมายถึงวิธีส่งค่าไปยังฟังก์ชันโดยใช้อาร์กิวเมนต์ ในการผ่านค่าเราคัดลอกข้อมูลที่เก็บไว้ในตัวแปรที่เราระบุและมันช้ากว่าผ่านโดยอ้างอิง bcse t เขาข้อมูลจะถูกคัดลอก ของเราทำการเปลี่ยนแปลงข้อมูลที่คัดลอกข้อมูลต้นฉบับจะไม่ได้รับผลกระทบ ในการส่งผ่านโดยการอ้างอิงหรือผ่านที่อยู่เราจะส่งลิงค์โดยตรงไปยังตัวแปรเอง หรือผ่านตัวชี้ไปยังตัวแปร มันเร็วขึ้น bcse ใช้เวลาน้อยลง
นี่คือตัวอย่างที่แสดงให้เห็นถึงความแตกต่างระหว่างการส่งผ่านค่า - ตัวชี้ - การอ้างอิง :
void swap_by_value(int a, int b){
int temp;
temp = a;
a = b;
b = temp;
}
void swap_by_pointer(int *a, int *b){
int temp;
temp = *a;
*a = *b;
*b = temp;
}
void swap_by_reference(int &a, int &b){
int temp;
temp = a;
a = b;
b = temp;
}
int main(void){
int arg1 = 1, arg2 = 2;
swap_by_value(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 1 2
swap_by_pointer(&arg1, &arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
arg1 = 1; //reset values
arg2 = 2;
swap_by_reference(arg1, arg2);
cout << arg1 << " " << arg2 << endl; //prints 2 1
}
“การผ่านโดยการอ้างอิง” วิธีการมีข้อ จำกัด ที่สำคัญ ถ้าพารามิเตอร์ถูกประกาศเป็นผ่านอ้างอิง (จึงนำหน้าด้วยและเข้าสู่ระบบ) มันสอดคล้องพารามิเตอร์ที่เกิดขึ้นจริงจะต้องเป็นตัวแปร
พารามิเตอร์จริงที่อ้างอิงถึงพารามิเตอร์ทางการของ "การส่งผ่านตามค่า" อาจเป็นนิพจน์โดยทั่วไปดังนั้นจึงอนุญาตให้ใช้ไม่เพียง แต่ตัวแปรเท่านั้น แต่ยังเป็นผลลัพธ์ที่แท้จริง
ฟังก์ชันไม่สามารถวางค่าในสิ่งอื่นที่ไม่ใช่ตัวแปร ไม่สามารถกำหนดค่าใหม่ให้กับตัวอักษรหรือบังคับให้นิพจน์เปลี่ยนผลลัพธ์ได้
PS: คุณสามารถตรวจสอบคำตอบ Dylan Beattie ในหัวข้อปัจจุบันที่อธิบายด้วยคำพูดธรรมดา