Con trỏ phần 2: Sử dụng con trỏ với hàm trong lập trình C

C. Sử dụng con trỏ với hàm trong lập trình C

Truyền tham số cho hàm bằng tham trị và tham chiếu

Truyền tham số bằng tham trị tức là truyền bản sao của biến vào hàm để xử lý. Bản sao của một biến mang giá trị bằng giá trị của biến đó.

Ví dụ

#include <stdio.h>

void truyen_tham_tri(int x){
  x++;
  printf("\nDia chi cua x = %d", &x);
}

int main(){
int a = 3; 
      printf("\nDia chi cua a = %d", &a);
printf("\nGia tri cua a truoc khi goi ham = %d", a);
truyen_tham_tri(a);
printf("\nGia tri cua a sau khi goi ham = %d", a);

 /* output
Dia chi cua a = -392092044
Gia tri cua a truoc khi goi ham = 3
Dia chi cua x = -392092068
Gia tri cua a sau khi goi ham = 3

*/
}

Như bạn thấy, khi hàm được truyền theo tham trị thì:

Giá trị của biến a trong hàm main không bị thay đổi. Địa chỉ của a trong hàm main và địa chỉ của x trong hàm truyen_tham_tri là khác nhau.

Vậy là chúng ta đã tạo ra thêm biến x mới trong bộ nhớ và điều này trả giá bằng chi phí bộ nhớ. Chúng ta có cách nào khác để tiết kiệm bộ nhớ hơn không?

Có, đó là gọi hàm bằng tham chiếu. Chúng ta truyền địa chỉ hoặc tham chiếu của biến vào hàm, lúc này không có bản sao chép nào được tạo cả, đảm bảo việc không bị lãng phí bộ nhớ. Chúng ta có thể truy cập vào giá trị lưu trong các địa chỉ này bằng toán tử tham chiếu ngược *.

Chúng ta có thể viết lại chương trình phía trên bằng cách gọi hàm bằng tham chiếu như sau:

#include <stdio.h>

void truyen_tham_chieu(int &x){
  x++;
  printf("\nDia chi cua x = %d", &x);
}

int main(){
int a = 3; 
      printf("\nDia chi cua a = %d", &a);
printf("\nGia tri cua a truoc khi goi ham = %d", a);
truyen_tham_chieu(a);
printf("\nGia tri cua a sau khi goi ham = %d", a);

 /* output
Dia chi cua a = 119268420
Gia tri cua a truoc khi goi ham = 3
Dia chi cua x = 119268420
Gia tri cua a sau khi goi ham = 4

*/
}

Như bạn thấy, khi hàm nhận tham số là tham chiếu thì:

Giá trị của biến a trong hàm main bị thay đổi đúng theo cách biến x bị thay đổi trong hàm truyen_tham_chieu.
Địa chỉ của a trong hàm main và địa chỉ của x trong hàm truyen_tham_chieu là giống nhau => chúng cùng là 1 biến.

Tham trị và tham chiếu cần phải được sử dụng chính xác nếu không bạn sẽ gặp phải những lỗi tai hại, hãy cùng xem xét ví dụ sau đây:


#include <stdio.h>

void swap(int &a, int &b){ // ham so nhan tham chieu
  int tmp = a;
  a = b;
  b = tmp;
printf("\nTrong ham: a = %d, b = %d", a, b);
}

int main(){
int first = 3,second = 5; 
printf("\ntruoc khi goi ham: first = %d, second = %d", first, second);
swap(first,second);
printf("\n Sau khi thuc thi ham: first = %d, second = %d", first, second);

 /* output
Truoc khi goi ham: first = 3, second = 5
Trong ham: a = 5, b = 3
Sau khi thuc thi ham: first = 5, second = 3 
*/
}

Chương trình trên thực hiện việc hoán đổi giá trị của 2 số first, second thông qua hàm swap(int a, int b) nhận vào 2 tham số là tham chiếu. Do đó, giá trị của biến first, second được hoán đổi sau khi thực thi hàm.

Tuy nhiên một lỗi khá thường gặp là bạn sơ ý truyền vào tham trị thay vì tham chiếu.

#include <stdio.h>

void swap(int a, int b){ // ham so nhan tham tri
  int tmp = a;
  a = b;
  b = tmp;
printf("\nTrong ham: a = %d, b = %d", a, b);
}

main(){
int first = 3,second = 5; 
printf("\ntruoc khi goi ham: first = %d, second = %d", first, second);
swap(first,second);
printf("\n Sau khi thuc thi ham: first = %d, second = %d", first, second);

 /* output
Truoc khi goi ham: first = 3, second = 5
Trong ham: a = 5, b = 3
Sau khi thuc thi ham: first = 3, second = 5 
*/
}

Hàm swap(int a, int b) nhận vào 2 tham số là tham trị. Do đó, giá trị của biến first, second không được hoán đổi sau khi thực thi hàm.

Sử dụng con trỏ để truyền mảng làm tham số của hàm Trong phần này, chúng ta hãy cùng tìm hiểu các chương trình khác nhau nhưng việc truyền tham số sẽ sử dụng con trỏ.

#include <stdio.h>

 void add(float *a, float *b){
 float c = *a + *b;
 printf("Addition gives %.2f\n",c);
}
int main(){
    printf("Enter two numbers :\n");
    float a,b;
    scanf("%f %f",&a,&b);
    add(&a,&b);
}

Chúng ta tạo hàm add() để tính tổng của 2 số a và b.

Địa chỉ của a và b được truyền vào hàm. Bên trong hàm chúng ta sử dụng * để truy cập giá trị của chúng và in ra kết quả.

Tương tự như vậy, chúng ta có thể truyền 1 mảng làm tham số bằng cách sử dụng con trỏ mà trỏ tới phần tử đầu tiên của nó.

#include <stdio.h>

void timSoLonNhat( int *p){
 int max = *p;
 for(int i=0; i < 5; i++){
    if(*(p+i) > max)
        max = *(p+i);
 }
 printf("so lon nhat la %d\n",max);
 }
main(){
  int myNumbers[5] = { 34, 65, -456, 0, 3455};
  timSoLonNhat(myNumbers);
   /* output:so lon nhat la 3455" */
}

Bởi vì tên của mảng, bản thân nó là 1 con trỏ trỏ tới phần tử đầu tiên, chúng ta có thể truyền nó như là 1 tham số tới hàm timSoLonNhat(). Trong hàm số này, chúng ta đã duyệt từng phần tử của mảng bằng con trỏ và vòng lặp để tìm ra số lớn nhất.

Về phần con trỏ và hàm, còn nhiều ứng dụng nữa mà chúng ta cần phải quan tâm. Tuy nhiên mình cho rằng nó vượt quá mục đích của bài viết này là giới thiệu về con trỏ cho người mới, mình sẽ để link tham khảo cho các bạn đọc thêm nhé. Con trỏ là một phần khó trong C nhưng nếu bạn hiểu và vận dụng được nó tốt thì chắc chắn bạn đang nắm trong tay kiến thức khá vững chắc về ngôn ngữ C cũng như cách tổ chức bộ nhớ đấy!

Kết bài

Vậy là chúng ta đã đi qua các định nghĩa, bản chất, lỗi thường gặp, các toán tử cũng như là ứng dụng của con trỏ. Hy vọng bài viết này đã giúp cho các bạn phần nào hiểu thêm về con trỏ và cách sử dụng nó trong lập trình C. Bản thân mình khi học về con trỏ cũng phải nhào nặn lại kiến thức khá nhiều thì mới có thể tận dụng được sức mạnh của nó. Nên các bạn đừng lo lắng hay ngại ngần mà đọc lại bài viết để nắm rõ hơn phần kiến thức hay và quan trọng này nhé!