Truyền array vào function trong Java

Em chào mọi người,

Chuyện là em mới học thì hiểu là array khi pass vào function sẽ chính là truyền vào địa chỉ của biến ở main.
Vì thế đối với WhatsPrinted04, kết quả sẽ ra 11.
Tuy nhiên cho em hỏi vì sao WhatsPrinted05 lại cho ra kết quả là 10 ạ?
Theo em được hiểu chỗ phần A = B không phải là gắn địa chỉ ở vị trí 0 của B[] cho vị trí o của A[] sao.

Ví dụ thứ 2 mà em muốn hỏi là ở phần WhatsPrinted08, em chạy thử code để xem thế nào. Và phần em thắc mắc là tại sao d = new Derp (222) lại không thay đổi giá trị 10 của x ban đầu.
Em cảm ơn

hh

ewr

1 Like

Bạn cần tìm hiểu kĩ hơn về cách Java truyền tham số cho method. Nó không giống cách C++ dùng địa chỉ hay tham chiếu (khá giống con trỏ thì đúng hơn).

Ví dụ 5 truyền object cho method. Bạn cần hiểu object gồm 2 phần là vùng nhớ và biến tham chiếu (giống pointer). Khi truyền vào method Java sẽ tạo ra biến tham chiếu copy để dùng. Do đó, bạn lấy bản copy trỏ đi đâu thì bản gốc (bên ngoài) không thay đổi.

Ví dụ 8, bạn thử tính xem có bao nhiêu object tạo ra. Phép gán của bạn chỉ là trỏ d tới vùng nhớ mới, và d hoàn toàn khác với d1 như ví dụ 5 nên bên ngoài không ảnh hưởng.

8 Likes

Em cảm ơn nhiều ạ.
Như cách anh giải thích cho em thì:

  • Ở ví dụ 4, A[0]++ sẽ làm thay đổi giá trị vì nó trỏ thẳng tới địa chỉ vị trí 0 (?)
  • Trong khi ở ví dụ 5, A = B; tức là A ở đây từ bản chất là địa chỉ (khi truyền vào ở phần int A[] tại func) đã chuyển sang bản copy (?). Hay nói dễ hiểu hơn là giá trị của array chỉ thay đổi khi được truy cập chính xác vào vị trí.
  • Vì ban đầu em hiểu A= B là giống việc trong C, ptr A ở vị trí 0 sẽ bằng ptr vị trí 0 của thằng B.
  • Ở ví dụ 8 như cách giải thích bên trên, tức là khi dùng biến cũ trỏ đến vùng nhớ mới tức là biến cũ sẽ tạo ra vùng bộ nhớ copy và thao tác trên Derp mới.
    Như nội dung phần này thì keyword là cách hoạt động của truyền tham số vào method phải không ạ?

Đây là lí do mình prefer bạn nghĩ về “con trỏ” thay vì “địa chỉ”, vì có thể có 2 con trỏ cùng trỏ vào một nơi, còn địa chỉ khi nói “bản copy” thì hơi mơ hồ. Với con trỏ, copy ở đây là tạo ra 1 con trỏ mới cùng trỏ đến vùng nhớ cũ.

Uh tạm hiểu vậy cũng được. Trong Java không có pointer nên dùng từ biến tham chiếu (reference variable). Và trỏ thì nên hiểu trỏ tới toàn bộ array (hoặc object), chứ không trỏ tới phần tử vị trí 0 như C/C++.

Nope, lúc Java truyền giá trị lúc nào cũng pass by value, nghĩa là đều tạo ra bản copy hết. Ví dụ 5 do bạn đặt tên A ở ngoài và tham số A trùng nhau nên khó phân biệt. Mình dùng ví dụ 8 cho dễ hiểu.

void func(Derp d) {
    d = new Derp(222);
}
// ...
Derp d1 = new Derp(10);
func(d1);

Bên ngoài, d1 là object nên gồm biến tham chiếu tên d1 và vùng nhớ có số 10. Khi gọi func() truyền vào d1 nghĩa là Java sẽ tạo ra một biến tham chiếu copy, tên là d và cùng trỏ tới vùng nhớ của d1.

Nhưng ở bên trong method, bạn lại new vùng nhớ mới chứa số 222, và dùng bản copy là d trỏ vào đó. Nên bản copy chỉ dùng được bên trong method thôi, còn d1 ở ngoài vẫn trỏ tới vùng nhớ số 10 cũ.

Ví dụ 5 cũng tương tự.

5 Likes

Nếu bạn giải thích như vậy thì đồng nghĩa với việc kết quả trả về sẽ trả về vùng tham chiếu mới rồi.
Mình học C chứ chưa qua Java nên không hiểu lắm.
Nếu vậy thì sự khác nhau giữa việc bạn gán A[i]++ và A = B là gì để dẫn đến sự khác nhau trong KQ trả về

Bạn xem lại phần copy biến tham chiếu ở trên mình có giải thích.

Do tham chiếu copy vẫn trỏ tới vùng nhớ cũ, nên khi tăng lên thì vùng nhớ bên ngoài bị thay đổi. Còn phép gán là dùng con trỏ copy trỏ qua vùng nhớ khác, vùng nhớ cũ không ảnh hưởng.

2 Likes

Em vẫn chưa hiểu rõ lắm.
Nếu tổng quát tức là khi pass array vào function thì nó luôn tạo ra bản copy. Và em chỉ có thể thay đổi khi em trỏ trực tiếp vào 1 địa chỉ ô nhớ xác định.
Ví dụ A = B thì không được, nhưng A[0] ++ hoặc A[0] = B[0] thì có thể.
Em có check ví dụ này để làm thử thì

void capitalize(char *s)
{
    int index = 1;
    char *result;
    result = (char*)malloc(strlen(s) + 1);
    char *sample = result;
    
    *sample = toupper(s[0]);
    sample++;
    while(s[index] != '\0')
    {
        if(s[index] == ' ')
        {
            *sample = s[index];
            sample++;
            index++;
            *sample = toupper(s[index]);
            index++;
            sample++;
            continue;
        }
        *sample = tolower(s[index]);
        index++;
        sample++;
    }
    *sample = '\0';
    printf("%s", result);
    strcpy(s, result);
    
}

Em nghĩ code của em đúng rồi, vì dòng print đã in ra “Hello Moi Nguoi” từ “hello moi nguoi”.
Vấn đề em thấy mình gặp phải nhất tức là em không biết làm sao để biết chính xác là trả về được KQ ra bên ngoài hay không.
Chẳng hạn như câu trên em dùng thử 3 cái:

  • Cách 1: A = B (anh đã giải thích là không hiệu quả)
  • Cách 2: Em xài thử strcpy vẫn không được
  • Cách 3: Em xài s[0] trỏ vào result[0] để nó tham chiếu vào vị trí đầu tiên.

Cũng từ vấn đề này mà em nghĩ đến việc người ta sử dụng char *str = “hello” bất kì.
Thì bản chất nó chính là const char = “hello”.
Mà đã là const thì làm sao người ta có thể thay đổi được. Hay khi anh học sâu vào thì mỗi khi người khác test thử hoặc làm việc với string họ chỉ dùng array character

Bạn mới học hay gì mà viết dài vậy.
Mình thấy C có thể gán giá trị trực tiếp được mà chứ đâu cần làm vậy, cứ check cái nào thì tự chỉnh trên s[i] đó luôn

Nếu em đổi thành như vậy thì vẫn hiệu quả khi pass array vào parameter nhưng nếu pass vào biến ptr, với biến ptr được khai là

char *ptr = "hello"

thì nó sẽ không thay đổi được, có phải vì em sai từ phần đó vì char ở đây là const không

Nếu là em sai vì cách truyền biến vào thì em nghĩ em có thể làm đơn giản thế này

void capitalize(char *s)
{
    int index = 1;
    s[0] = toupper(s[0]);
    while(s[index] != '\0')
    {
        if(s[index] == ' ')
        {
            index++;
            s[index] = toupper(s[index]);
            index++;
            continue;
        }
        s[index] = tolower(s[index]);
        index++;
    }
    s[index] = '\0';
    printf("%s", s);
}

Hóng bạn khác rành C++ vào tư vấn tiếp, phần này mình không rành.

Và nếu bạn muốn học Java thì nên tránh mang tư tưởng từ bên C++ qua, nhất là phần quản lý bộ nhớ.

3 Likes

1. :point_right: Java đã bỏ khái niệm con trỏ như trong C do không có nhu cầu quản lý bộ nhớ thủ công và cũng không có nhu cầu lập trình những gì liên quan tầng bậc thấp (Cái này mình không chắc 100% vì mình chưa từng học C) thay vào đó java có khái niệm PASS BY VALUEPASS BY REFERENCE Vấn đề này có nhiều topic (view khủng) thảo luận nhiều rồi https://daynhauhoc.com/search?q=pass%20by%20value

2. :point_right: Có 3 vấn bạn cần tìm hiểu thật kỹ:
2.1. java có 2 kiểu dữ liệu là primitive data type và reference data type. Bạn cần tìm hiểu xem java lưu 2 loại này vào RAM như thế nào.
2.2. Bạn phải tìm hiểu kỹ stack và heap là gì, liên quan gì đến cái trên.
2.3. Cuối cùng truyền tham trị (pass by value) và truyền tham chiếu (pass by reference) liên quan gì đến hai mục ở trên.

3. :point_right: Bạn cần đọc kỹ 2 link bên dưới, lấy giấy bút vẽ 2 vùng nhớ stack, heap ra, vẽ từng ô nhớ trên stack, từng ô nhớ trên heap và kéo mũi tên lại với nhau thì sẽ hiểu. (Kỹ tính hơn thì vẽ luôn stack frame trên stack khi chạy mỗi function)
toithacmac.wordpress.com/2015/04/18/bai-3-su-khac-nhau-giua-kieu-du-lieu-co-so-va-kieu-du-lieu-tham-chieu-trong-ngon-ngu-java-la-gi

yellowcodebooks.com/2021/06/14/lam-ban-ve-tham-chieu-tham-tri-trong-java

4. :point_right: Phần thực hành:
Sau khi đã đọc xong lý thuyết thì lấy giấy bút ra mô phỏng từng dòng chương trình sau và giải thích sự thay đổi của a, b, arr1, arr2 trước khi vào hàm và sau khi ra khỏi hàm (bằng các khái niệm bên trên)

	static void function(int a, int b, int[] arr1, int[] arr2) {
		a = b;                                    //1
		arr1[0] = 9999;                           //2
		arr2 = new int[] { 123, 456 };            //3
	}

	public static void main(String[] args) {
		int a = 1, b = 2;                         //1'
		int[] arr1 = new int[] { 1, 2, 3 };       //2'
		int[] arr2 = new int[] { 7, 8, 9 };		  //3'

		function(a, b, arr1, arr2);
	}

Cách hiểu bên trên cũng đúng với javascript. Mình cũng không rõ C, C++ thế nào nên không thể so sánh.

Case này em xin phép xin ý kiến anh @tntxtnt :grin:

2 Likes

sao đang hỏi Java mà thành C là sao :V ko biết trả lời cái gì :V :V :V

Java chỉ có pass by value như C thôi nha, rất đơn giản. Trừ mấy cái primitive types ra là value thật, còn lại cứ object là pass reference by value :V Cứ hiểu reference types là T* như bên C là xong :V Java ko có truyền con trỏ 2 cấp nên đơn giản còn hơn C :V Đừng lầm với pass by reference bên C++ khác biệt hoàn toàn với C và Java (ngay cả khởi điểm của C++ là C with classes cũng ko có khái niệm pass by reference này vì lúc đó C++ chưa có reference) :V

tóm lại là object có kiểu là T -> hiểu theo cách của code C thì Java nó truyền T* object, còn kiểu nguyên thủy (int, double, v.v…) thì nó truyền là T primitiveType :V

6 Likes

à ờm bổ sung thêm có thể truyền con trỏ 2 cấp hay n cấp trong Java bằng cách tạo 1 wrapper class bọc quanh con trỏ n-1 cấp đó =]] ví dụ

    static class PointerToInt {
        int value;
        PointerToInt(int n) {
            this.value = n;
        }
        void deref_assign(int n) {
            this.value = n;
        }
        int deref() {
            return this.value;
        }
    }
    public static void swap(PointerToInt a, PointerToInt b) {
        int temp = a.deref();
        a.deref_assign(b.deref());
        b.deref_assign(temp);
    }
    public static void main(String args[]) {
        PointerToInt a = new PointerToInt(11);
        PointerToInt b = new PointerToInt(22);
        System.out.println(a.deref()); // 11
        System.out.println(b.deref()); // 22
        swap(a, b);
        System.out.println(a.deref()); // 22
        System.out.println(b.deref()); // 11
    }

ko truyền int* được thì tạo 1 wrapper class có cho phép dereference như *p và deref rồi gán như *p = ... là xong :relieved:

Java có class Integer cũng là wrapper của int nhưng ko chó phép deref giá trị bên dưới vì nhiều lý do :V 1 trong các lý do đó có lẽ là optimization, ví dụ small int như 0-255 thì Java nó tạo 256 object sẵn, mỗi lần tạo Integer.valueOf(n) nếu n nhỏ nó chỉ việc ref tới object 1 sẵn có là xong ko cần cấp phát gì hết :V n lớn thì mới cấp phát động. Nhưng optimize kiểu vậy mà cho deref integer value bên dưới thì Integer(1) có thể bị sửa thành 11, thành ra gọi Integer.valueOf(1) mà nó in ra 11 thì chết nên ko cho deref :V


thử đổi int value thành int[] value xem có “swap” được ko :V

được nè mà phải sử dụng đúng :V


    static class PointerToIntArray {
        int[] value;
        PointerToIntArray(int[] arr) {
            this.value = arr;
        }
        void deref_assign(int[] newValue) {
            this.value = newValue;
        }
        int[] deref() {
            return this.value;
        }
    }
    public static void swap(PointerToIntArray a, PointerToIntArray b) {
        int[] temp = a.deref();
        a.deref_assign(b.deref());
        b.deref_assign(temp);
    }
    public static void main(String args[]) {
        PointerToIntArray a = new PointerToIntArray(new int[3]);
        a.deref()[0] = 10;
        a.deref()[1] = 20;
        a.deref()[2] = 30;
        PointerToIntArray b = new PointerToIntArray(new int[3]);
        b.deref()[0] = 11;
        b.deref()[1] = 22;
        b.deref()[2] = 33;
        System.out.println(Arrays.toString(a.deref())); // [10, 20, 30]
        System.out.println(Arrays.toString(b.deref())); // [11, 22, 33]
        swap(a, b);
        System.out.println(Arrays.toString(a.deref())); // [11, 22, 33]
        System.out.println(Arrays.toString(b.deref())); // [10, 20, 30]
    }

nếu tạo a = new PointerToIntArray(A) với int[] A = {10, 20, 30}; ở ngoài thì swap vẫn ko thay đổi được giá trị của A, vì a.value chỉ là 1 con trỏ khác cùng trỏ tới mảng mà A trỏ tới. Nếu a.value thay đổi thì A vẫn ko bị thay đổi :V

4 Likes

có nghĩ là nếu mình truy cập 1 ví trí index của mảng A thì có thể trỏ tới và sửa giá trị của nó được , nhưng mà lấy cả mảng B gán cho A thì sẽ sinh ra 1 vùng nhớ "A phụ " để chứa thằng B đúng không ạ , và sau khi hàm kết thúc thì “A phụ” sẽ được giải phóng, còn A thì vẫn y nguyên , theo ý e hiểu nãy giờ là vậy

Không, Java không làm như vậy. A sẽ cùng trỏ vào chỗ mà B đang trỏ vào.

Nói cách khác, lệnh gán A = B không tác động đến (cả hai) đối tượng được trỏ.

6 Likes

WhatsPrinted05
mình sẽ đặt là A trong hàm main, và A local trong WhatsPrinted05 , lúc truyền giá trị vào hàm nó sẽ thực hiện A (local) = A ( trong hàm main) , lúc này A local sẽ tham chiếu đến cùng vùng nhớ với A trong hàm main, sau đó gán A (local) = B , lúc này A local sẽ ko tham chiếu đến cùng vùng nhớ A hàm main nữa, mà chuyển sang tham chiếu đến vùng nhớ B, nên vùng nhớ mà A trong hàm main tham chiếu đến chả thay đổi gì >>> print ra 10

WhatsPrinted08
tương tự luôn , d ( local ) = d1 ( trong hàm main) , sau đó lại cho d (local) tham chiếu đến vùng nhớ khác, tất nhiên vùng nhớ mà d1 trong hàm main đang tham chiếu đến chả thay đổi gì

5 Likes
83% thành viên diễn đàn không hỏi bài tập, còn bạn thì sao?