Tham chiếu và tham trị trong lập trình

Khi mới học lập trình, bạn thường nghe về tham chiếu và tham trị. Mặc dù hiểu rõ lý thuyết, nhiều lập trình viên lại ít chú ý đến chúng trong thực tế, đặc biệt khi fix bug. Sau đây chúng ta hãy cùng nhau tìm hiểu kỹ hơn về hai khái niệm này qua các ví dụ trong C#.

Tham chiếu và tham trị là gì?

Tham chiếu

Tham chiếu liên quan đến việc sử dụng một biến để trỏ đến vùng nhớ của một biến khác. Nghĩa là khi chúng ta có một biến tham chiếu, biến này chỉ chứa địa chỉ và “chỉ” đến vùng nhớ của biến khác. Lúc này, bất kỳ sự thay đổi nào được thực hiện trên tham chiếu sẽ ảnh hưởng đến giá trị của biến gốc.

Ví dụ, giả sử bạn có một biến x có giá trị là 10. Bây giờ, bạn tạo một tham chiếu y đến x. Nếu bạn thay đổi giá trị của y, giá trị của x cũng sẽ thay đổi theo.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
using System;

public class Program
{
public static void Main()
{
int x = 10;
ref int y = ref x; // Tham chiếu đến x

y = 20; // Thay đổi giá trị của y
Console.WriteLine(x); // Kết quả: 20

// khi thay đổi x, y đang trỏ tới x nên y sẽ có giá trị theo x
x = 30;
Console.WriteLine(y); // Kết quả: 30
}
}

Trong ví dụ này, y là một tham chiếu đến x, do đó khi giá trị của y thay đổi, giá trị của x cũng thay đổi theo và ngược lại.

Tham trị

Ngược lại, tham trị là việc truyền giá trị thực sự của biến vào một biến khác hoặc một hàm. Khi bạn gán giá trị của biến này cho biến khác, một bản sao giá trị của biến được tạo ra và gán vào biến mới. Lúc này, bất kỳ thay đổi nào được thực hiện trên tham trị sẽ không ảnh hưởng đến giá trị của biến gốc.

Ví dụ, khi một bản sao của giá trị biến được tạo ra và sử dụng trong hàm, nó không ảnh hưởng đến biến gốc.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
using System;

public class Program
{
public static void ChangeValue(int y)
{
y = 20; // Thay đổi giá trị của y trong hàm
}

public static void Main()
{
int x = 10;
int z = x;
ChangeValue(x); // Truyền x như là tham trị

Console.WriteLine(x); // Kết quả: 10

z = 30;
Console.WriteLine(x); // Kết quả: 10

x = 20;
Console.WriteLine(z); // Kết quả: 30
}
}

Trong ví dụ này, giá trị của x không bị thay đổi khi truyền vào hàm ChangeValuex được truyền theo tham trị, tạo ra một bản sao của x.

Tại sao chúng ta lại ít để ý?

Hai khái niệm này thể hiện rõ nhất khi làm việc với ngôn ngữ C hoặc C++, do đặc thù của các ngôn ngữ này liên quan tới con trỏ và quản lý cấp phát vùng nhớ. Tuy nhiên, với các ngôn ngữ bậc cao như C# và JavaScript, lập trình viên thường không cần trực tiếp quản lý con trỏ, dẫn đến việc ít chú ý hơn đến sự khác biệt giữa tham chiếu và tham trị.

Trong dự án mình đang tham gia, tôi nhận thấy rằng khoảng 40% lỗi ở frontend (đang dùng React) liên quan đến việc quản lý các giá trị trong bộ nhớ không hợp lý. Việc này xuất phát từ việc không hiểu sâu về React và không nắm rõ tham chiếu, tham trị của biến và data. Điều này dẫn đến việc sửa lỗi một chỗ lại làm xuất hiện lỗi ở chỗ khác, tạo ra vòng luẩn quẩn gây tốn kém thời gian và tiền bạc.

Trong JavaScript và C#, chúng ta có hai loại data types chính: kiểu giá trị nguyên thủy và kiểu giá trị tham chiếu. Kiểu giá trị nguyên thủy (tham trị) bao gồm: string, number, boolean, … Kiểu giá trị tham chiếu bao gồm: object, array, function, …

Ví dụ, khi bạn khai báo một object là x, và bạn gán y = x, thì lúc này thực chất cả xy đang cùng trỏ tới một vùng nhớ (tham chiếu).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function updateValue(obj) {
obj.value = 30;
}

function main() {
let x = { value: 10 };
let y = x;
// x và y đang trỏ tới cùng 1 object

console.log(y.value); // 10

updateValue(y);

console.log(x.value); // 30

updateValue(x);

console.log(y.value); // 30
}

main();

Trong ví dụ này, xy đều trỏ đến cùng một đối tượng. Khi giá trị của y thay đổi qua hàm updateValue, giá trị của x cũng thay đổi theo.

Chúng ta thường nói JavaScript có những điều kỳ quặc, ví dụ như khi so sánh ===, lúc thì true, lúc thì false, không biết đường nào mà lần. Thực ra nếu hiểu bản chất, chúng ta sẽ thấy nó không khó lắm. Ví dụ:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
let a = [];
let b = [];
// a và b tuy rỗng, nhưng vẫn khác nhau

let c = [1, 2, 3];
let d = c;
// c và d đang trỏ tới cùng một vùng nhớ

let e = [1, 2, 3];
let f = [1, 2, 3];
// e và f tuy hai mảng giống, nhưng đang trỏ tới hai vùng nhớ khác nhau

console.log(a === b); // false
console.log(c === d); // true
console.log(e === f); // false
console.log(e[0] === f[0]); // true

Qua các ví dụ và giải thích trên, hy vọng các bạn đã hiểu rõ hơn về tham chiếu và tham trị. Việc nắm vững các khái niệm này không chỉ giúp bạn tránh lỗi khi lập trình mà còn tối ưu hóa việc quản lý bộ nhớ và hiệu suất của ứng dụng.

 Comments
Comment plugin failed to load
Loading comment plugin