/ 365วันแห่งโปรแกรม

[365 วันแห่งโปรแกรม #day14] Parameter

วันที่สิบสี่ของ ‪#‎365วันแห่งโปรแกรม‬ วันนี้เราจะมีคุยกันเรื่อง Parameter


เราอาจจะรู้จักคำว่า parameter มานานแล้ว แล้วเราก็รู้อีกว่ามันหมายถึงข้อมูลที่เราส่งเข้าไปใน function/method บางคนอาจจะได้ยินคำว่า formal parameter และ actual parameter แล้ว 2 อย่างนี้มันต่างกันยังไง? วันนี้เรามาดูกันครับ

formal parameter คือ input variable ในุมมองของ function/method ซึ่งค่าของตัวแปรเหล่านี้ถูกส่งมาโดย caller ของ function/method นี้

actual parameter คือข้อมูลจริงที่จะส่งเข้าไปใน function/method ในบางครั้ง actual parameter ก็ถูกเรียกว่า arguments

function add(var a, var b){
	return a+b;
}

var val1 = 5;
var result = add(val1, 10);

จากตัวอย่าง ตัวแปรที่อยู่ในส่วนประกาศของ add() ได้แก่ a และ b เรียกว่า formal parameter ซึ่งทั้งสองตัวนี้เป้นตัวแทนของ val1 และ 10 ที่ส่งเข้าไปโดย caller เราเรียก val1 และ 10 ว่า actual parameter

ในโลกของ Parameter เรายังสามารถแบ่งชนิดของ parameter ออกเป็น 2 แบบ คือ Value Parameter และ Variable Parameter แต่ก่อนที่เราจะเข้าไปดูรายละเอียดตรงนั้น เราต้องย้อนกลับไปถึงเรื่องตัวแปรเสียก่อน

Value-Type vs Reference-Type

เราสามารถจำแนกตัวแปรด้วยวิธีการเก็บข้อมูลของมันได้เป็น Value-Type และ Reference-Type

Value-Type

ตัวแปรแบบ Value จะเก็บค่าที่ถูก assign ให้โดยตรง ตัวอย่างของตัวแปรประเภทนี้คือ struct (รวม primitive data type), และ enum

int a = 5;
int b = a; //b = 5
b = 10;

จากตัวอย่างข้างบน เมื่อเราเราบอกว่า b = a แล้ว โปรแกรมจะ copy ค่า 5 (ค่าของ a) ไปเก็บไว้อีกที่สำหรับตัวแปร b แล้วหลังจากนั้นเมื่อเราบอกว่า b = 10 ค่า 10 ก็จะไปทับ ค่าเก่า (5) โดยไม่มีผลกระทบต่อค่าของตัวแปร a แต่อย่างใด

Reference-Type

ตัวแปรแบบ Reference ไม่ได้เก็บค่าของตัวแปรโดยตรง แต่จะเก็บ reference ของค่านั้นแทน ข้อมูลที่ตัวแปรประเภทนี้ชี้ไปจะเป็นข้อมูลที่ complex เช่น object, array, delegate

class Foo{
	int count = 0;
	void Bar(){
    	count++;
	}
}

var c = new Foo();
var d = c;
d.Bar(); 
print(d.count); //1
print(c.count); //1
d = new Foo();
print(d.count); //0
print(c.count); //1

==output==
1
1
0
1

จากตัวอย่างข้างบน เมื่อเราเราบอกว่า d = c แล้ว โปรแกรมจะทำการคัดลอก reference ที่ c ถือไว้ มาให้ d ต่อมาเมื่อเราสั่ง d.Bar() Foo ที่ d อ้างถึงอยู่ก็จะเรียกใช้ Bar(); และค่า count จะเพิ่มขึ้น 1 เมื่อเราลองพิมพ์ค่า count ของ d ดูก็จะพบว่าได้ 1 หลังจากนั้นถ้าเราลองพิมพ์ค่า count ของ c ดู ก็จะได้ 1 เช่นกัน เพราะ c และ d อ้างถึง object ตัวเดียวกัน ไม่ว่าเราจะสั่งงานที่ไหนก็เหมือนกัน ต่อมาเราสั่งให้ d = new Foo(); หมายถึงว่าให้สร้าง Foo ตัวใหม่ขึ้นมา และให้ d ชี้ไป หลังจากนั้นเราสั่ง print อีกครั้ง ก็จะพบว่า count ของ d เป็น 0 ส่วนของ c เป็น 1 ก็เพราะตอนนี้ c และ d อ้างถึง object คนละก้อนแล้ว

Value Parameter vs Variable Parameter

เรามาต่อกันที่เรื่อง Value Parameter และ Variable Parameter กันดีกว่า

Value Parameter

โดยทั่วไปแล้ว Parameter จะเป็น Value Parameter หมายความว่าค่าที่เราใส่ใน actual parameter จะถูก copy ไปให้ formal parameter และเมื่อเราเปลี่ยนแปลงค่าของ formal parameter แล้วจะไม่ส่งผลกระทบต่อ ค่าที่ใส่เป็น actual parameter เราเรียกการส่งค่าเข้าใน method/function แบบนี้ว่า pass by value

void Foo(int a){
     a = 10;
	print(a); //10
}

int val1 = 5;
print(val1); //5
Foo(val1);
print(val1); //5

==output==
5
10
5

จากตัวอย่างด้านบน จะพบว่า เมื่อเราสร้างตัวแปร val1 เป็นชนิด int โดยกำหนดค่า 5 แล้วส่งเข้าไปใน Foo() โปรแกรมจะ copy ค่า 5 แล้วกำหนดให้ตัวแปร a หลังจากนั้นเราบอกว่าให้ a = 10 แล้ว ตัวแปร val1 จะไม่ถูกเปลี่ยนค่าตามไปด้วย

class Foo{
    int count = 0;
    void Bar(){
        count++;
    }
}

void FooBar(Foo a){
    a.Bar();
    print(a.count); 
}

var val1 = new Foo();
print(val1.count); //0
FooBar(val1);
print(val1.count); //1

==output==
0
1
1

จากตัวอย่างด้านบน จะพบว่า เมื่อเราสร้างตัวแปร val1 เป็นตัวแปรแบบ reference ของ object Foo แล้วลองพิมพ์ค่า count ออกมา ในครั้งแรก จะได้ 0 ซึ่งเป็นค่า default ต่อมาเราส่ง val1 เข้าไปในฟังก์ชัน FooBar() โปรแกรมจะทำการคัดลอก reference ของ val1 ไปใส่ให้ a หลังจากนั้นในฟังก์ชันมีการเรียกใช้ a.Bar(); ซึ่งจะทำให้ค่า count เพิ่มขึ้น เมื่อลองพิมพ์ดูก็จะได้ 1 หลังจากฟังก์ชันทำงานจบลงเราลองพิมพ์เพื่อเช็คค่า count ของ val1 ก็พบว่าเป็น 1 เช่นกัน เพราะ val1 และ a ใน FooBar() ชี้ไปยัง object ตัวเดียวกันนั่นเอง

หลังจากจบตัวอย่างทั้ง 2 อันแล้วเราจะพบว่า pass by value หมายถึงไม่ว่าค่าใน actual parameter จะเป็นอย่างไร ก็ให้ copy ไปใส่ formal parameter ให้หมด ในกรณีของตัวแปร reference มันไม่ได้เก็บ object จริงๆ แต่มันเก็บแค่ reference ที่ใช้ชี้ไปหา object ดังนั้นสิ่งที่ถูก copy จึงเป็น reference ไม่ใช่ object

Variable Parameter

ส่วน Variable Parameter หรือ Reference Parameter จะไม่มีการ copy ค่าจาก actual parameter ไปให้ formal parameter แต่อย่างใด แต่ formal parameter จะอ้างถึง actual parameter โดยตรง ซึ่งทำให้ค่าของตัวแปรทั้งสองฝั่งเปลี่ยนแปลงตามกันตลอด

void Foo (ref int a)
{
    a = 0;
    print(a); //0
}

int val1 = 5;
print(val1); //5
Foo(ref val1);
print(val1); //0

==output==
5
0
0

ในตัวอย่างแรกนี้เราจะกาศตัวแปรแบบ Value int val1 = 5 หลังจากนั้นก็สั่งพิมพ์ค่าออกมา ซึ่งได้ 5 ตามที่ประกาศไว้ ต่อมาเราส่ง reference ของ val1 เข้าไปใน Foo() ซึ่งรับพารามิเตอร์แบบ reference ใน Foo() นั้นจะอ้างถึงค่าของ val1 ด้วย a เมื่อเราลองทำการกำหนดให้ a เป็น 0 แล้วพิมพ์ค่าดูก็จะได้ 0 หลังจากฟังก์ชันทำงานเสร็จ ลองพิมพ์ค่า val1 ดูอีกครั้ง ก็พบว่าได้ 0 เช่นกัน นั่นหมายความว่า a ใน Foo() ไม่ได้ copy ค่าของ val1 ไป แต่มันเป็นตัวแทนของ val1 จริงๆ เลย ทำให้เมื่อเราเปลี่ยนแปลงค่าของ a แล้วส่งผลกระทบไปยังค่าของ val1 ด้วย

class Foo{
    int count = 0;
    void Bar(){
        count++;
    }
}

void FooBar(ref Foo a){
    a.Bar();
    print(a.count); 
    a = new Foo();
    print(a.count); 
}

var val1 = new Foo();
print(val1.count); //0
FooBar(ref  val1);
print(val1.count); 

==output==
0
1
0
0

ในตัวอย่างต่อมาคราวนี้เราเปลี่ยนมาเล่นกับ reference type บ้าง วิธีการคล้ายๆ เดิม สร้าง val1 เป็น Foo แล้วพิมพ์ค่า count ก็จะได้ 0 ซึ่งเป็นค่า default ต่อมา ส่ง reference ของ val1 เข้าไปที่ฟังก์ชัน FooBar() ซึ่งรับพารามิเตรอ์ Foo แบบ reference ในชื่อของ a หลังจากนั้นสั่ง a.Bar(); เพื่อเพิ่มค่า count แล้วพิมพ์ดู ก็จะได้ 1 หลังจกนั้น กำหนดค่าให้ a ใหม่ด้วยการสร้าง object ของ Foo ใหม่เลย แล้วพิมพ์ค่า count อีกครั้ง ก็จะได้ 0 ซึ่งเป็นค่าเริ่มต้นของ Foo.count หลังจากฟังก์ชันนี้ทำงานเสร็จ เราลิงพิมพ์ค่า count ของ val1 ดูอีกครั้งปรากฏว่า ได้ 0 ซึ่งเป็นค่า count ของ object ใหม่ที่สร้างใน FooBar() นั่นหมายความว่า ในการ pass by reference แม้ว่าพารามอเตอร์ที่ส่งเข้าไปจะเป็น reference type ฝั่ง formal parameter ก็ยังสามารถอ้างถึงค่า actual parameter ที่ส่งเข้ามาจริงๆ ได้ เสมือนว่าเป็นตัวแปรเดียวกัน

ในแต่ละภาษาใช้วิธีบอกว่า function นี้รับ Variable Parameter ไม่เหมือนกัน
ข้อควรระวัง เราไม่สามารถส่งค่าที่ไม่ใช่ตัวแปรเข้าไปใน method/function ที่รับ Variable Parameter ได้

สรุป

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

References

Parameter passing in Java

Parameter passing in C#

Passing by Reference in PHP

Passing Parameters (C# Programming Guide)

‪#‎day14 #365วันแห่งโปรแกรม ‪#‎โครงการ365วันแห่ง‬...