ผลต่างระหว่างรุ่นของ "Psl/การส่งค่าไปยังฟังก์ชัน"
Jittat (คุย | มีส่วนร่วม) |
Jittat (คุย | มีส่วนร่วม) |
||
(ไม่แสดง 8 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน) | |||
แถว 1: | แถว 1: | ||
− | : ''หน้านี้เป็นส่วนหนึ่งของวิชา [[Problem solving lab]]'' | + | : ''หน้านี้เป็นส่วนหนึ่งของวิชา [[Problem solving lab]]'' shortcut:[[Psl/function-args|Psl/function-args]] |
== argument กับ parameter == | == argument กับ parameter == | ||
เพื่อความเข้าใจที่ตรงกัน เราจะพิจารณาความแตกต่างผ่านทางโค้ดด้านล่าง | เพื่อความเข้าใจที่ตรงกัน เราจะพิจารณาความแตกต่างผ่านทางโค้ดด้านล่าง | ||
− | <syntaxhighlight lang=" | + | <syntaxhighlight lang="cpp"> |
− | void f(int x) | + | void f(int x) // Line 1 |
+ | { | ||
//... | //... | ||
} | } | ||
− | main() { | + | main() |
+ | { | ||
int y = 10; | int y = 10; | ||
− | f(y); // Line | + | f(y); // Line 9 |
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | เรานิยมเรียก <tt>y</tt> ในบรรทัดที่ | + | เรานิยมเรียก <tt>y</tt> ในบรรทัดที่ 9 ว่า '''argument''' (เป็นตัวแปรหรือค่าที่ส่งให้กับฟังก์ชัน) ในขณะที่จะเรียก <tt>x</tt> ว่าเป็น '''parameter''' (เป็นตัวแปรที่เราประกาศพร้อมกับฟังก์ชันเพื่อเอาไว้รับค่า) |
'''หมายเหตุ''' ในการใช้ทั่วไปเราอาจจะใช้สลับกันได้ ถ้าต้องการให้ชัดเจนจริง ๆ เราจะเรียก parameter ที่ประกาศในฟังก์ชันว่า ''formal'' argument/parameter และเรียก argument ในการเรียกใช้ฟังก์ชันว่า ''actual'' argument/parameter (ตาม [https://stackoverflow.com/questions/156767/whats-the-difference-between-an-argument-and-a-parameter]) | '''หมายเหตุ''' ในการใช้ทั่วไปเราอาจจะใช้สลับกันได้ ถ้าต้องการให้ชัดเจนจริง ๆ เราจะเรียก parameter ที่ประกาศในฟังก์ชันว่า ''formal'' argument/parameter และเรียก argument ในการเรียกใช้ฟังก์ชันว่า ''actual'' argument/parameter (ตาม [https://stackoverflow.com/questions/156767/whats-the-difference-between-an-argument-and-a-parameter]) | ||
แถว 22: | แถว 24: | ||
ในภาษา C การส่งค่าไปยังฟังก์ชันจะเป็นการส่งแบบ pass by value นั่นคือค่าจาก argument จะถูก copy ไปยังเนื้อที่ที่จองไว้สำหรับ parameter ในฟังก์ชัน ทำให้เราไม่สามารถแก้ค่าของ argument กลับไปยังจุดที่เรียกฟังก์ชันได้ | ในภาษา C การส่งค่าไปยังฟังก์ชันจะเป็นการส่งแบบ pass by value นั่นคือค่าจาก argument จะถูก copy ไปยังเนื้อที่ที่จองไว้สำหรับ parameter ในฟังก์ชัน ทำให้เราไม่สามารถแก้ค่าของ argument กลับไปยังจุดที่เรียกฟังก์ชันได้ | ||
+ | <syntaxhighlight lang="cpp"> | ||
+ | void f(int x) | ||
+ | { | ||
+ | x = 100; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ในกรณีนี้ ไม่ว่าเราจะเรียกฟังก์ชัน f อย่างไร เราก็ไม่สามารถทำให้ f แก้ค่าตัวแปรที่ส่งค่ามาได้ เช่น | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | int y = 1000; | ||
+ | f(y); // at the end, y is still 1000. | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ซึ่งก็เป็นพฤติกรรมปกติ เมื่อเราคิดว่าเราส่งค่าคงที่ หรือค่าอื่น ๆ ให้กับ f ก็ได้เช่นกัน ดังตัวอย่างด้านล่าง | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | f(500); | ||
+ | f(y*y); | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ถ้าสามารถแก้ค่าจาก parameter x กลับมาได้ เราคงจะไม่รู้ว่าจะเอาค่ากลับมาที่ argument ที่ส่งไปได้อย่างไร | ||
+ | |||
+ | ด้วยเหตุผลที่กล่าวมา การพยายามจองค่า ListNode ในตัวอย่างด้านล่างจึงไม่สามารถส่งค่ากลับมาได้ | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | void create_new_node(ListNode* head) | ||
+ | { | ||
+ | if(head == 0) { | ||
+ | head = new ListNode(); | ||
+ | } | ||
+ | } | ||
+ | main() | ||
+ | { | ||
+ | // ... | ||
+ | ListNode* h = 0; | ||
+ | create_new_node(h); // after this, h is still 0. | ||
+ | // ... | ||
+ | } | ||
+ | </syntaxhighlight> | ||
== การใช้พอยน์เตอร์ในการส่งค่า == | == การใช้พอยน์เตอร์ในการส่งค่า == | ||
+ | ในภาษา C หนทางเดียวที่จะกลับไปแก้ค่าตัวแปรที่อยู่นอกฟังก์ชันได้ ก็คือการส่ง '''ตำแหน่ง''' (address) มาให้กับฟังก์ชันเลย นั่นคือหน้าที่หลักของตัวแปรประเภท pointer นี่เอง | ||
+ | |||
+ | ตัวอย่างเช่น | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | void ff(int* x) | ||
+ | { | ||
+ | (*x) = 100; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | เมื่อเราใช้ | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | int y = 1000; | ||
+ | ff(&y); // at the end, y is now 100 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ค่าของตัวแปร y ก็จะเปลี่ยนไปตามต้องการ สังเกตว่า เราไม่ได้ไปเปลี่ยนค่าตัวแปร x ใน ff เลย (ไม่ได้เปลี่ยน pointer) แต่เปลี่ยนค่าในหน่วยความจำที่ x ชี้อยู่เท่านั้น และในทำนองเดียวกัน เราส่ง pointer (&y) ไปยังตัวแปร y เราไม่ได้มีการแก้ pointer ดังกล่าวใน ff เช่นเดียวกัน | ||
+ | |||
+ | สังเกตว่าเราจะเขียนแบบนี้ไม่ได้ | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | ff(&100); // compilation error | ||
+ | ff(&(y*y)); // compilation error | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | เมื่อเรากลับมาพิจารณาตัวอย่างการสร้าง ListNode เราจะแก้โค้ดได้เป็น | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | void create_new_node(ListNode** head) | ||
+ | { | ||
+ | if((*head) == 0) { | ||
+ | (*head) = new ListNode(); | ||
+ | } | ||
+ | } | ||
+ | main() | ||
+ | { | ||
+ | // ... | ||
+ | ListNode* h = 0; | ||
+ | create_new_node(&h); // after this, h points to a new ListNode | ||
+ | // ... | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | เวลาเห็น type ที่มี * เยอะ ๆ อย่าเพิ่งตกใจ ค่อย ๆ อ่านย้อนไปก็จะเข้าใจได้ | ||
== การ pass by reference ใน C++ == | == การ pass by reference ใน C++ == | ||
+ | ใน C++ ได้เพิ่ม type รูปแบบใหม่คือ reference (อ่านเพิ่มที่ [https://en.wikipedia.org/wiki/Reference_(C%2B%2B)]) ทำให้สามารถส่งค่าแบบ reference ได้ | ||
+ | |||
+ | พิจารณาตัวอย่างด้านล่าง | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | void fff(int& x) | ||
+ | { | ||
+ | x = 100; | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | เมื่อเราใช้งาน | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | int y = 1000; | ||
+ | fff(y); // at the end, y is now 100 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | หลักการทำงานนั้นแทบไม่ต่างจากการใช้ pointer ใน ff เลย ตัวแปรแบบ reference มีลักษณะคล้าย pointer แม้ไม่มีความสามารถมากเท่าแต่ก็ปลอดภัยในการใช้งานมากกว่า | ||
+ | |||
+ | ข้อเสียก็คือตอนที่เราเรียก fff เราจะไม่เห็นเลยว่ามีโอกาสที่เราจะแก้ค่าตัวแปร y (เพราะว่าเราทำเหมือน pass by value) | ||
+ | |||
+ | กลับมาพิจารณาตัวอย่างการสร้าง ListNode ถ้าเราใช้ pass by reference เราจะเขียนเป็น | ||
+ | |||
+ | <syntaxhighlight lang="cpp"> | ||
+ | void create_new_node(ListNode*& head) | ||
+ | { | ||
+ | if(head == 0) { | ||
+ | head = new ListNode(); | ||
+ | } | ||
+ | } | ||
+ | main() | ||
+ | { | ||
+ | // ... | ||
+ | ListNode* h = 0; | ||
+ | create_new_node(h); // after this, h points to a new ListNode | ||
+ | // ... | ||
+ | } | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | ให้ระวังลำดับของ * และ & ให้ดี ให้อ่านจากหลังมาหน้า (นั่นคือ "head เป็น reference ไปยัง pointer ไป ListNode") | ||
== การระบุว่าเป็นค่าคงที่ด้วย const == | == การระบุว่าเป็นค่าคงที่ด้วย const == | ||
+ | : ''จะมาเขียนเพิ่ม'' |
รุ่นแก้ไขปัจจุบันเมื่อ 22:47, 3 กุมภาพันธ์ 2561
- หน้านี้เป็นส่วนหนึ่งของวิชา Problem solving lab shortcut:Psl/function-args
เนื้อหา
argument กับ parameter
เพื่อความเข้าใจที่ตรงกัน เราจะพิจารณาความแตกต่างผ่านทางโค้ดด้านล่าง
void f(int x) // Line 1
{
//...
}
main()
{
int y = 10;
f(y); // Line 9
}
เรานิยมเรียก y ในบรรทัดที่ 9 ว่า argument (เป็นตัวแปรหรือค่าที่ส่งให้กับฟังก์ชัน) ในขณะที่จะเรียก x ว่าเป็น parameter (เป็นตัวแปรที่เราประกาศพร้อมกับฟังก์ชันเพื่อเอาไว้รับค่า)
หมายเหตุ ในการใช้ทั่วไปเราอาจจะใช้สลับกันได้ ถ้าต้องการให้ชัดเจนจริง ๆ เราจะเรียก parameter ที่ประกาศในฟังก์ชันว่า formal argument/parameter และเรียก argument ในการเรียกใช้ฟังก์ชันว่า actual argument/parameter (ตาม [1])
การส่งค่าแบบ pass by value
ในภาษา C การส่งค่าไปยังฟังก์ชันจะเป็นการส่งแบบ pass by value นั่นคือค่าจาก argument จะถูก copy ไปยังเนื้อที่ที่จองไว้สำหรับ parameter ในฟังก์ชัน ทำให้เราไม่สามารถแก้ค่าของ argument กลับไปยังจุดที่เรียกฟังก์ชันได้
void f(int x)
{
x = 100;
}
ในกรณีนี้ ไม่ว่าเราจะเรียกฟังก์ชัน f อย่างไร เราก็ไม่สามารถทำให้ f แก้ค่าตัวแปรที่ส่งค่ามาได้ เช่น
int y = 1000;
f(y); // at the end, y is still 1000.
ซึ่งก็เป็นพฤติกรรมปกติ เมื่อเราคิดว่าเราส่งค่าคงที่ หรือค่าอื่น ๆ ให้กับ f ก็ได้เช่นกัน ดังตัวอย่างด้านล่าง
f(500);
f(y*y);
ถ้าสามารถแก้ค่าจาก parameter x กลับมาได้ เราคงจะไม่รู้ว่าจะเอาค่ากลับมาที่ argument ที่ส่งไปได้อย่างไร
ด้วยเหตุผลที่กล่าวมา การพยายามจองค่า ListNode ในตัวอย่างด้านล่างจึงไม่สามารถส่งค่ากลับมาได้
void create_new_node(ListNode* head)
{
if(head == 0) {
head = new ListNode();
}
}
main()
{
// ...
ListNode* h = 0;
create_new_node(h); // after this, h is still 0.
// ...
}
การใช้พอยน์เตอร์ในการส่งค่า
ในภาษา C หนทางเดียวที่จะกลับไปแก้ค่าตัวแปรที่อยู่นอกฟังก์ชันได้ ก็คือการส่ง ตำแหน่ง (address) มาให้กับฟังก์ชันเลย นั่นคือหน้าที่หลักของตัวแปรประเภท pointer นี่เอง
ตัวอย่างเช่น
void ff(int* x)
{
(*x) = 100;
}
เมื่อเราใช้
int y = 1000;
ff(&y); // at the end, y is now 100
ค่าของตัวแปร y ก็จะเปลี่ยนไปตามต้องการ สังเกตว่า เราไม่ได้ไปเปลี่ยนค่าตัวแปร x ใน ff เลย (ไม่ได้เปลี่ยน pointer) แต่เปลี่ยนค่าในหน่วยความจำที่ x ชี้อยู่เท่านั้น และในทำนองเดียวกัน เราส่ง pointer (&y) ไปยังตัวแปร y เราไม่ได้มีการแก้ pointer ดังกล่าวใน ff เช่นเดียวกัน
สังเกตว่าเราจะเขียนแบบนี้ไม่ได้
ff(&100); // compilation error
ff(&(y*y)); // compilation error
เมื่อเรากลับมาพิจารณาตัวอย่างการสร้าง ListNode เราจะแก้โค้ดได้เป็น
void create_new_node(ListNode** head)
{
if((*head) == 0) {
(*head) = new ListNode();
}
}
main()
{
// ...
ListNode* h = 0;
create_new_node(&h); // after this, h points to a new ListNode
// ...
}
เวลาเห็น type ที่มี * เยอะ ๆ อย่าเพิ่งตกใจ ค่อย ๆ อ่านย้อนไปก็จะเข้าใจได้
การ pass by reference ใน C++
ใน C++ ได้เพิ่ม type รูปแบบใหม่คือ reference (อ่านเพิ่มที่ [2]) ทำให้สามารถส่งค่าแบบ reference ได้
พิจารณาตัวอย่างด้านล่าง
void fff(int& x)
{
x = 100;
}
เมื่อเราใช้งาน
int y = 1000;
fff(y); // at the end, y is now 100
หลักการทำงานนั้นแทบไม่ต่างจากการใช้ pointer ใน ff เลย ตัวแปรแบบ reference มีลักษณะคล้าย pointer แม้ไม่มีความสามารถมากเท่าแต่ก็ปลอดภัยในการใช้งานมากกว่า
ข้อเสียก็คือตอนที่เราเรียก fff เราจะไม่เห็นเลยว่ามีโอกาสที่เราจะแก้ค่าตัวแปร y (เพราะว่าเราทำเหมือน pass by value)
กลับมาพิจารณาตัวอย่างการสร้าง ListNode ถ้าเราใช้ pass by reference เราจะเขียนเป็น
void create_new_node(ListNode*& head)
{
if(head == 0) {
head = new ListNode();
}
}
main()
{
// ...
ListNode* h = 0;
create_new_node(h); // after this, h points to a new ListNode
// ...
}
ให้ระวังลำดับของ * และ & ให้ดี ให้อ่านจากหลังมาหน้า (นั่นคือ "head เป็น reference ไปยัง pointer ไป ListNode")
การระบุว่าเป็นค่าคงที่ด้วย const
- จะมาเขียนเพิ่ม