Hướng dẫn pass by reference c++ - chuyển bằng tham chiếu c ++

Mở đầu

Khi học một ngôn ngữ lập trình, một trong những thứ bạn phải nắm được đó là ngôn ngữ đó truyền biến vào hàm bằng cách nào, khi thao tác với biến đó trong hàm thì có ảnh hưởng tới biến nằm ngoài hàm hay không? Điều này là rất cần thiết để tránh những khó hiểu về sau, nhất là những người đang code một ngôn ngữ quen rồi nhảy sang ngôn ngữ khác học.

Show

Ví dụ 2 đoạn code dưới đây giống nhau nhưng kết quả lại trả về khác nhau cho mỗi ngôn ngữ:

// javascript

function test(b) {
  b[0] = 3
}

var a = [1, 2]
test(a)
console.log(a)  // [3, 2]
// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]

Các ngôn ngữ khác nhau có cách xử lý với các tham số truyền vào khác nhau nhưng có 2 loại truyền tham số đó là truyền theo giá trị (pass-by-value) hay truyền theo tham chiếu (pass-by-reference). Có một số ngôn ngữ có thêm khái niệm pass-by-sharing nhưng khái niệm này thực chất cùng bản chất với 2 khái niệm trên.tham số truyền vào khác nhau nhưng có 2 loại truyền tham số đó là truyền theo giá trị (pass-by-value) hay truyền theo tham chiếu (pass-by-reference). Có một số ngôn ngữ có thêm khái niệm pass-by-sharing nhưng khái niệm này thực chất cùng bản chất với 2 khái niệm trên.

Trước khi đọc bài này, cần nắm được cơ bản một số thứ sau:

  • Mutable và immutable là gì?
  • Biến được lưu trữ trên stack như thế nào?

Mình sẽ trình bày cho một số ngôn ngữ là C, C++, js, ruby, java, php.C, C++, js, ruby, java, php.

Phần giải thích dưới đây mình cố gắng bám sát vào việc mô tả bộ nhớ để trình bày nên có lẽ sẽ hơi khó hiểu vì chỉ có bảng mà không có hình ảnh trực quan.

Nếu bạn muốn bỏ qua phần mô tả bộ nhớ và xem bằng hình ảnh thì nhảy xuống phần Mô tả bằng hình ảnh.

Kiến thức chung

  • CPU xử lý dữ liệu thông qua địa chỉ bộ nhớ nên thứ được truyền vào hàm luôn luôn là địa chỉ bộ nhớ chứ không phải là giá trị. xử lý dữ liệu thông qua địa chỉ bộ nhớ nên thứ được truyền vào hàm luôn luôn là địa chỉ bộ nhớ chứ không phải là giá trị.
  • Pass-by-value và pass-by-reference không có định nghĩa cụ thể nào và có thể hiểu khác nhau với từng ngôn ngữ. Nhưng đều có chung một nguyên lý là: và pass-by-reference không có định nghĩa cụ thể nào và có thể hiểu khác nhau với từng ngôn ngữ. Nhưng đều có chung một nguyên lý là:
  • Pass-by-value được hiểu là khi bạn thay đổi biến trong hàm thì ngoài hàm sẽ không bị ảnh hưởng. Nó giống như bạn copy giá trị của biến vào biến khác rồi truyền vào hàm. được hiểu là khi bạn thay đổi biến trong hàm thì ngoài hàm sẽ không bị ảnh hưởng. Nó giống như bạn copy giá trị của biến vào biến khác rồi truyền vào hàm.
  • Pass-by-reference là khi bạn thay đổi biến trong hàm cũng làm ngoài hàm bị ảnh hưởng. Nó giống như bạn truyền đúng địa chỉ của biến đó vào hàm. là khi bạn thay đổi biến trong hàm cũng làm ngoài hàm bị ảnh hưởng. Nó giống như bạn truyền đúng địa chỉ của biến đó vào hàm.
  • Khi chương trình thực thi, dữ liệu trên RAM có thể được lưu trữ trên stack hoặc heap nhưng việc tham chiếu bằng địa chỉ giữa các biến là như nhau nên để cho đơn giản mình sẽ giả sử chúng chỉ được lưu trữ trên stack. RAM có thể được lưu trữ trên stack hoặc heap nhưng việc tham chiếu bằng địa chỉ giữa các biến là như nhau nên để cho đơn giản mình sẽ giả sử chúng chỉ được lưu trữ trên stack.
  • Trong tất cả ngôn ngữ, khi khai báo một hàm thì tham số của hàm có thể khác hoặc trùng với tên biến được truyền vào hàm., khi khai báo một hàm thì tham số của hàm có thể khác hoặc trùng với tên biến được truyền vào hàm.

Ví dụ 2 đoạn code dưới đây là hoàn toàn như nhau:

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}

C, C++

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
4

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
5.

C và C++ pass-by-value như thế nào?

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}

Biến

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 trên trong bảng symbol table sẽ như sau:

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 1

Khi gọi hàm

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
7 từ hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
8 thì biến
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 sẽ được copy thành một biến mới và địa chỉ của biến mới này sẽ được truyền vào hàm
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
0:

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 1
Khi gọi hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
7 từ hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
8 thì biến
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 sẽ được copy thành một biến mới và địa chỉ của biến mới này sẽ được truyền vào hàm
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
0:
b 1

ssss2 CC++ có kiểu intmutable nên câu lệnh

var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
1 sẽ tạo:

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 1
Khi gọi hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
7 từ hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
8 thì biến
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 sẽ được copy thành một biến mới và địa chỉ của biến mới này sẽ được truyền vào hàm
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
0:
b 2

ssss2

Vì C và C++ có kiểu int là mutable nên câu lệnh

var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
1 sẽ tạo:

Địa chỉ của a trước khi truyền vào hàm: -1730233380
Địa chỉ của b trước khi gán: -1730233412
Địa chỉ của b sau khi gán: -1730233412
Địa chỉ của a sau khi gọi hàm: -1730233380
Giá trị của a sau khi gọi hàm: 1

Như bạn thấy biến

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 vẫn trỏ tới
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
3 và vẫn có giá trị là
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
4 không hề bị ảnh hưởng.C sẽ biết C có thể thay đổi giá trị của biến ngoài hàm bằng cách truyền con trỏ (pointer), điều này vẫn đúng khi nói C là pass-by-value vì:

Đoạn code trên sẽ in ra như sau:

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int* b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  printf("Giá trị của b trước khi gán: %d\n", b);
  printf("Giá trị của a ở trong hàm trước khi gán: %d\n\n", *b);

  *b = 2;

  printf("Địa chỉ của b sau khi gán: %d\n", &b);
  printf("Giá trị của b sau khi gán: %d\n", b);
  printf("Giá trị của a ở trong hàm sau khi gán: %d\n\n", *b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n\n", &a);  
    
    test(&a); // vì b trong test() đã là kiểu con trỏ nên phải truyền địa chỉ của a vào.
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}

Nhưng nếu ai đã code C sẽ biết C có thể thay đổi giá trị của biến ngoài hàm bằng cách truyền con trỏ (pointer), điều này vẫn đúng khi nói C là pass-by-value vì:

  • Con trỏ (pointer) có phải là pass-by-value không?
  • Khi gọi
    var a = 1
    pass(a)
    
    function pass(b) {
      // dùng b ở đây
    }
    5 thì:biến khác và truyền địa chỉ của biến khác này vào hàm
    var a = 1
    pass(a)
    
    function pass(b) {
      // dùng b ở đây
    }
    0 (trong hàm
    var a = 1
    pass(a)
    
    function pass(b) {
      // dùng b ở đây
    }
    0 thì địa chỉ của biến khác này chính là
    // Đoạn code này giống nhau trong C và C++
    
    #include 
    #include 
    
    void test(int b) {
      printf("Địa chỉ của b trước khi gán: %d\n", &b);
      b = 2;
      printf("Địa chỉ của b sau khi gán: %d\n", &b);
    }
        
    int main()
    {
        int a = 1;
        printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
        
        test(a);
        
        printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
        printf("Giá trị của a sau khi gọi hàm: %d\n", a);
    }
    0).
Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 1
Khi gọi hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
7 từ hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
8 thì biến
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 sẽ được copy thành một biến mới và địa chỉ của biến mới này sẽ được truyền vào hàm
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
0:
b ssss1

Khi gọi hàm

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
7 từ hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
8 thì biến
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 sẽ được copy thành một biến mới và địa chỉ của biến mới này sẽ được truyền vào hàm
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
0:

b

ssss2

Vì C và C++ có kiểu int là mutable nên câu lệnh

var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
1 sẽ tạo:symbol table sẽ như sau:

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 2
Khi gọi hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
7 từ hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
8 thì biến
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 sẽ được copy thành một biến mới và địa chỉ của biến mới này sẽ được truyền vào hàm
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
0:
b ssss1

Khi gọi hàm

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
7 từ hàm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
8 thì biến
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 sẽ được copy thành một biến mới và địa chỉ của biến mới này sẽ được truyền vào hàm
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
0:

Địa chỉ của a trước khi truyền vào hàm: -1375358500                                                                                    
                                                                                                                                       
Địa chỉ của b trước khi gán: -1375358536                                                                                               
Giá trị của b trước khi gán: -1375358500                                                                                               
Giá trị của a ở trong hàm trước khi gán: 1                                                                                             
                                                                                                                                       
Địa chỉ của b sau khi gán: -1375358536                                                                                                 
Giá trị của b sau khi gán: -1375358500                                                                                                 
Giá trị của a ở trong hàm sau khi gán: 2                                                                                               
                                                                                                                                       
Địa chỉ của a sau khi gọi hàm: -1375358500                                                                                             
Giá trị của a sau khi gọi hàm: 2

b con trỏ vào hàm thì vẫn copy địa chỉ sang biến khác rồi truyền vào hàm nên nó vẫn là pass-by-value (một số người gọi truyền theo pointer là pass-by-address).

ssss2

Vì C và C++ có kiểu int là mutable nên câu lệnh

var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
1 sẽ tạo:C nếu muốn thay đổi giá trị của biến ngoài hàm từ trong hàm thì phải truyền con trỏ như giải thích ở trên. Nhược điểm của phương pháp này là:

  • Phải tạo thêm một vùng không gian nhớ cho pointer trong stack.pointer trong stack.
  • Cú pháp xấu vì luôn phải gắn asterisk (dấu *) vào trước tên biến.asterisk (dấu *) vào trước tên biến.

VD: 

#include 
#include 

void test(int* x, int* y) {
  *x = *x + 5;
  *y = *y + 5;
}

int main() {
  int x = 1;
  int y = 2;
  test(&x, &y);
  printf("x: %d\n", x);  // 6
  printf("y: %d\n", y);  // 7
}

Trong C++ có giải pháp thay thế là pass-by-reference giúp ít nhất là tránh các nhược điểm trên:C++ có giải pháp thay thế là pass-by-reference giúp ít nhất là tránh các nhược điểm trên:

#include 
#include 

void test(int &x, int &y) {
  x = x + 5;
  y = y + 5;
}

int main() {
  int x = 1;
  int y = 2;
  test(x, y);
  printf("x: %d\n", x);  // 6
  printf("y: %d\n", y);  // 7
}

Với đoạn code ở phần trên dùng pass-by-reference:pass-by-reference:

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
0

Lúc này symbol table trước khi thực thi sẽ như sau:symbol table trước khi thực thi sẽ như sau:

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 1
b ssss1 b

1

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 2
b ssss1 b

1 địa chỉ stack nên gán

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 ở trong hàm cũng làm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 ở ngoài hàm bị thay đổi.

Sau khi thực thi sẽ như sau:symbol table trên có thêm biến

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 nhưng trong stack không phải cấp phát thêm một vùng bộ nhớ cho
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 vì sử dụng chung
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
3 với
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6.

2

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
1

Vì var a = 1 pass(a) function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào. // dùng a ở đây. }6 và // Đoạn code này giống nhau trong C và C++ #include #include void test(int b) { printf("Địa chỉ của b trước khi gán: %d\n", &b); b = 2; printf("Địa chỉ của b sau khi gán: %d\n", &b); } int main() { int a = 1; printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a); test(a); printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a); printf("Giá trị của a sau khi gọi hàm: %d\n", a); }0 cùng trỏ đến một địa chỉ stack nên gán // Đoạn code này giống nhau trong C và C++ #include #include void test(int b) { printf("Địa chỉ của b trước khi gán: %d\n", &b); b = 2; printf("Địa chỉ của b sau khi gán: %d\n", &b); } int main() { int a = 1; printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a); test(a); printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a); printf("Giá trị của a sau khi gọi hàm: %d\n", a); }0 ở trong hàm cũng làm var a = 1 pass(a) function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào. // dùng a ở đây. }6 ở ngoài hàm bị thay đổi.

  • Trong bảng symbol table trên có thêm biến
    // Đoạn code này giống nhau trong C và C++
    
    #include 
    #include 
    
    void test(int b) {
      printf("Địa chỉ của b trước khi gán: %d\n", &b);
      b = 2;
      printf("Địa chỉ của b sau khi gán: %d\n", &b);
    }
        
    int main()
    {
        int a = 1;
        printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
        
        test(a);
        
        printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
        printf("Giá trị của a sau khi gọi hàm: %d\n", a);
    }
    0 nhưng trong stack không phải cấp phát thêm một vùng bộ nhớ cho
    // Đoạn code này giống nhau trong C và C++
    
    #include 
    #include 
    
    void test(int b) {
      printf("Địa chỉ của b trước khi gán: %d\n", &b);
      b = 2;
      printf("Địa chỉ của b sau khi gán: %d\n", &b);
    }
        
    int main()
    {
        int a = 1;
        printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
        
        test(a);
        
        printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
        printf("Giá trị của a sau khi gọi hàm: %d\n", a);
    }
    0 vì sử dụng chung
    var a = 1
    pass(a)
    
    function pass(b) {
      // dùng b ở đây
    }
    3 với
    var a = 1
    pass(a)
    
    function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
      // dùng a ở đây.
    }
    6.primitive trong js đều là immutable.
  • Javascriptprimitive với nhau thì chúng sẽ so sánh giá trị.
// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
2
  • Một số thứ cần biết trong jsobject (không phải các kiểu primitive) thì chúng sẽ so sánh địa chỉ của 2 object đó với nhau.
// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
3

Tất cả các kiểu primitive trong js đều là immutable.

Khi so sánh 2 biến có kiểu primitive với nhau thì chúng sẽ so sánh giá trị.pass-by-value, cũng nhiều người nói js vừa có pass-by-value, vừa có pass-by-reference. Nhưng:

Địa chỉ của a trước khi truyền vào hàm: -1730233380
Địa chỉ của b trước khi gán: -1730233412
Địa chỉ của b sau khi gán: -1730233412
Địa chỉ của a sau khi gọi hàm: -1730233380
Giá trị của a sau khi gọi hàm: 1
9

Nhưng khi so sánh 2 kiểu object (không phải các kiểu primitive) thì chúng sẽ so sánh địa chỉ của 2 object đó với nhau. truyền cùng một địa chỉ với biến vào hàm chứ không copy sang biến khác giống C.

Javascript pass-by-value hay pass-by-referenceobject a và object b bằng nhau nên chúng sẽ có chung một địa chỉ trên stack.

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 b
b ssss1 b
1 Sau khi thực thi sẽ như sau:2

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 và
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 cùng trỏ đến một địa chỉ stack nên gán
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 ở trong hàm cũng làm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 ở ngoài hàm bị thay đổi.

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 b
1 Sau khi thực thi sẽ như sau:2
b 1 b
1 Sau khi thực thi sẽ như sau:2

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 và
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 cùng trỏ đến một địa chỉ stack nên gán
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 ở trong hàm cũng làm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 ở ngoài hàm bị thay đổi.

Trong bảng symbol table trên có thêm biến

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 nhưng trong stack không phải cấp phát thêm một vùng bộ nhớ cho
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 vì sử dụng chung
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
3 với
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6.

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
4

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int* b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  printf("Giá trị của b trước khi gán: %d\n", b);
  printf("Giá trị của a ở trong hàm trước khi gán: %d\n\n", *b);

  *b = 2;

  printf("Địa chỉ của b sau khi gán: %d\n", &b);
  printf("Giá trị của b sau khi gán: %d\n", b);
  printf("Giá trị của a ở trong hàm sau khi gán: %d\n\n", *b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n\n", &a);  
    
    test(&a); // vì b trong test() đã là kiểu con trỏ nên phải truyền địa chỉ của a vào.
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
6

Javascript

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 3
b ssss1 3

b

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 3
b 1 4


Sau khi thực thi sẽ như sau:

2

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
5

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 và
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 cùng trỏ đến một địa chỉ stack nên gán
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 ở trong hàm cũng làm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 ở ngoài hàm bị thay đổi.

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
6

Trong bảng symbol table trên có thêm biến

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 nhưng trong stack không phải cấp phát thêm một vùng bộ nhớ cho
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 vì sử dụng chung
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
3 với
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6.stack:

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 b
b ssss1 b
1 Sau khi thực thi sẽ như sau:2


var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 và
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 cùng trỏ đến một địa chỉ stack nên gán
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 ở trong hàm cũng làm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 ở ngoài hàm bị thay đổi.

Trong bảng symbol table trên có thêm biến

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 nhưng trong stack không phải cấp phát thêm một vùng bộ nhớ cho
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 vì sử dụng chung
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
3 với
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6.changed", lúc đó stack sẽ thành:

Tên biếnĐịa chỉ của biến trên stackGiá trị của biến trong stack
a ssss1 Javascript
b ssss1 Javascript
Một số thứ cần biết trong jsSau khi thực thi sẽ như sau:2
1 Sau khi thực thi sẽ như sau:2

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 và
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 cùng trỏ đến một địa chỉ stack nên gán
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 ở trong hàm cũng làm
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6 ở ngoài hàm bị thay đổi.immutable.

Trong bảng symbol table trên có thêm biến

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 nhưng trong stack không phải cấp phát thêm một vùng bộ nhớ cho
// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 vì sử dụng chung
var a = 1
pass(a)

function pass(b) {
  // dùng b ở đây
}
3 với
var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
6.

Javascript

Một số thứ cần biết trong js

Tất cả các kiểu primitive trong js đều là immutable.

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
7

Khi so sánh 2 biến có kiểu primitive với nhau thì chúng sẽ so sánh giá trị.object_id của từng object:

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
8

Nhưng khi so sánh 2 kiểu object (không phải các kiểu primitive) thì chúng sẽ so sánh địa chỉ của 2 object đó với nhau.

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
9

Javascript pass-by-value hay pass-by-reference

Nhiều người nói js chỉ pass-by-value, cũng nhiều người nói js vừa có pass-by-value, vừa có pass-by-reference. Nhưng: giống C++, cũng copy ra biến mới rồi mới truyền vào hàm (kể cả array).

Javascript truyền cùng một địa chỉ với biến vào hàm chứ không copy sang biến khác giống C.PHP5 trở đi thì kiểu object truyền vào hàm sẽ giống như javascript là truyền tham chiếu vào.

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
0

Ở ví dụ trên, trước khi

// Đoạn code này giống nhau trong C và C++

#include 
#include 

void test(int b) {
  printf("Địa chỉ của b trước khi gán: %d\n", &b);
  b = 2;
  printf("Địa chỉ của b sau khi gán: %d\n", &b);
}
    
int main()
{
    int a = 1;
    printf("Địa chỉ của a trước khi truyền vào hàm: %d\n", &a);    
    
    test(a);
    
    printf("Địa chỉ của a sau khi gọi hàm: %d\n", &a);
    printf("Giá trị của a sau khi gọi hàm: %d\n", a);
}
0 bị gán lại thì object a và object b bằng nhau nên chúng sẽ có chung một địa chỉ trên stack.object tương tự như javascript, còn giải thích cho các kiểu còn lại tương tự như C và C++.

{item: [1, 2]}

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
1

ssss1.item

var a = 1
pass(a)

function pass(a) { // tham số cũng có tên là a, trùng với tên biến sẽ được truyền vào.
  // dùng a ở đây.
}
2

yyyy1  giải thích hoàn toàn giống javascript.

[1, 2] java ta có thể gán bằng các phương thức mutable hoặc immutable nên symbol table có thể khác nhau phụ thuộc vào phép gán.

Mô tả bằng hình ảnh

Mình chỉ minh họa bằng hình ảnh đại diện cho đoạn javascript sau:javascript sau:

// PHP

function test($b) {
  $b[0] = 3;
}

$a = [1, 2];
test($a);
var_dump($a)  // [1, 2]
1

Trước khi phép gán xảy ra:

Hướng dẫn pass by reference c++ - chuyển bằng tham chiếu c ++

Hình ảnh mô tả cho đoạn code javascript phía trên tương tự với phần lớn các ngôn ngữ khác nhưng khác với C/C++. Như đã nói  ở phần trên, trong C/C++ thì biến

#include 
#include 

void test(int &x, int &y) {
  x = x + 5;
  y = y + 5;
}

int main() {
  int x = 1;
  int y = 2;
  test(x, y);
  printf("x: %d\n", x);  // 6
  printf("y: %d\n", y);  // 7
}
1 sẽ được copy nên không trỏ vào cùng một ô nhớ với các biến khai báo giống trong hình ảnh.javascript phía trên tương tự với phần lớn các ngôn ngữ khác nhưng khác với C/C++. Như đã nói  ở phần trên, trong C/C++ thì biến
#include 
#include 

void test(int &x, int &y) {
  x = x + 5;
  y = y + 5;
}

int main() {
  int x = 1;
  int y = 2;
  test(x, y);
  printf("x: %d\n", x);  // 6
  printf("y: %d\n", y);  // 7
}
1 sẽ được copy nên không trỏ vào cùng một ô nhớ với các biến khai báo giống trong hình ảnh.

Sau khi phép gán xảy ra:

Hướng dẫn pass by reference c++ - chuyển bằng tham chiếu c ++

Link tham khảo:

https://stackoverflow.com/questions/2229498/passing-by-reference-in-c

https://developer.mozilla.org/en-US/docs/Web/JavaScript/Equality_comparisons_and_sameness