ผลต่างระหว่างรุ่นของ "Psl/การส่งค่าไปยังฟังก์ชัน"

จาก Theory Wiki
ไปยังการนำทาง ไปยังการค้นหา
 
(ไม่แสดง 7 รุ่นระหว่างกลางโดยผู้ใช้คนเดียวกัน)
แถว 1: แถว 1:
: ''หน้านี้เป็นส่วนหนึ่งของวิชา [[Problem solving lab]]''
+
: ''หน้านี้เป็นส่วนหนึ่งของวิชา [[Problem solving lab]]'' shortcut:[[Psl/function-args|Psl/function-args]]
  
 
== argument กับ parameter ==
 
== argument กับ parameter ==
แถว 5: แถว 5:
  
 
<syntaxhighlight lang="cpp">
 
<syntaxhighlight lang="cpp">
void f(int x) {          // Line 1
+
void f(int x)             // Line 1
 +
{
 
   //...
 
   //...
 
}
 
}
  
main() {
+
main()  
 +
{
 
   int y = 10;
 
   int y = 10;
   f(y);                  // Line 7
+
   f(y);                  // Line 9
 
}
 
}
 
</syntaxhighlight>
 
</syntaxhighlight>
  
เรานิยมเรียก <tt>y</tt> ในบรรทัดที่ 7 ว่า '''argument''' (เป็นตัวแปรหรือค่าที่ส่งให้กับฟังก์ชัน)  ในขณะที่จะเรียก <tt>x</tt> ว่าเป็น '''parameter''' (เป็นตัวแปรที่เราประกาศพร้อมกับฟังก์ชันเพื่อเอาไว้รับค่า)
+
เรานิยมเรียก <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

จะมาเขียนเพิ่ม