Lập trình C/C++

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

Tham trị và tham chiếu trong lập trình C/C++
Được viết bởi Minh Hoàng

Series lập trình C/C++, ngôn ngữ lập trình hệ thống mạnh mẽ.

– Trong bài viết này chúng ta sẽ cùng tìm hiểu về 3 cách truyền tham số cho hàm trong lập trình C/C++, với những nội dung sau:

  1. Tham số truyền bằng Tham trị (dùng trong cả C và C++)
  2. Tham số truyền bằng Tham chiếu (CHỈ DÙNG trong C++)
  3. Tham số truyền bằng Con trỏ (dùng trong cả C và C++)

– Hàm trong C/C++ hoạt động theo nguyên tắc:
Khi gọi hàm, 1 bản sao của tham số được tạo ra, sau đó cấp phát vùng nhớ mới, copy giá trị sang (quá trình này được gọi là shadow copy), và hàm sẽ làm việc với bản sao này. (Trong C++ nó sẽ dùng hàm tạo sao chép để tiến hành quá trình shadow copy này)

Có thể bạn quan tâm: Cách cấp phát và giải phóng bộ nhớ trong lập trình C.

1. Tham số truyền bằng Tham trị (dùng trong cả C và C++)
1. Tham số truyền bằng Tham trị (dùng trong cả C và C++)

– Khi tham số của hàm được truyền bằng tham trị tức là truyền giá trị cho hàm, khi đi vào hàm sẽ tạo ra 1 vùng nhớ mới và mọi thao tác tính toán chỉ thao tác trên vùng nhớ mới đó, còn vùng nhớ cũ vẫn không thay đổi. Cho nên sau khi thực hiện xong, ra khỏi hàm thì giá trị của biến được truyền vào làm tham số sẽ không thay đổi, do không có ảnh hưởng trực tiếp đến vùng nhớ cũ.

=> Tham trị sẽ không làm thay đổi giá trị sau khi kết thúc lời gọi hàm. Do đó, tham trị được sử dụng khi không có nhu cầu thay đổi giá trị của tham số truyền vào.

[code language=”c” highlight=”4″] #include <stdio.h>

// Truyền tham trị
void Xuly(int a)
{
a = a + 10;

printf("Dia chi cua bien a trong ham Xuly: %p\n", &a);
printf("Gia tri cua bien a trong ham Xuly: %d\n", a);
}

int main()
{
int a = 5;

Xuly( a );

printf("\nDia chi cua bien a trong ham main: %p\n", &a);
printf("Gia tri cua bien a trong ham main: %d\n", a);

getchar();
return 0;
}
[/code]

 

Kết quả chương trình

Kết quả chương trình

– Từ kết quả chạy chương trình các bạn có thể thấy, địa chỉ của biến a trong hàm XuLy(012FF64C) và trong hàm main(012FF720) là khác nhau nên việc tính toán bên trong hàm XuLy sẽ không làm ảnh hưởng đến giá trị a ở hàm main.

2. Tham số truyền bằng Tham chiếu (CHỈ DÙNG trong C++)
2. Tham số truyền bằng Tham chiếu (CHỈ DÙNG trong C++)

– Khi tham số của hàm được truyền bằng tham chiếu tức là truyền địa chỉ cho hàm, do đó khi đi vào hàm mọi thao tác tính toán sẽ được thực hiện trên vùng nhớ cũ, cho nên sau khi ra khỏi hàm giá trị đã bị thay đổi.

=> Tham chiếu sẽ làm thay đổi giá trị sau khi kết thúc lời gọi hàm. Do đó, tham chiếu được sử dụng khi có nhu cầu thay đổi giá trị của tham số truyền vào.

[code language=”c” highlight=”4″] #include <iostream>

// Truyền tham chiếu
void Xuly(int &a)
{
a = a + 10;

printf("Dia chi cua bien a trong ham Xuly: %p\n", &a);
printf("Gia tri cua bien a trong ham Xuly: %d\n", a);
}

int main()
{
int a = 5;

Xuly( a );

printf("\nDia chi cua bien a trong ham main: %p\n", &a);
printf("Gia tri cua bien a trong ham main: %d\n", a);

system( "pause" );
return 0;
}
[/code]

 

Kết quả chương trình

Kết quả chương trình

– Từ kết quả chạy chương trình các bạn có thể thấy, địa chỉ của biến a trong hàm XuLy và hàm main là giống nhau(00AFFA20) nên việc tính toán bên trong hàm XuLy thì sẽ ảnh hưởng trực tiếp và làm thay đổi giá trị của biến a ở hàm main (cả 2 đều bằng 15).

3. Tham số truyền bằng Con trỏ (dùng trong cả C và C++)
3. Tham số truyền bằng Con trỏ (dùng trong cả C và C++)

Có thể bạn quan tâm: Cách sử dụng con trỏ trong lập trình C/C++.

[code language=”c” highlight=”5″] #include <stdio.h>

// Truyền con trỏ
// *n: toán tử * ở đây biểu thị n là 1 BIẾN CON TRỎ
void Xuly(int *n)
{
// Bên trong hàm:
// *n: toán tử * dùng để lấy GIÁ TRỊ của biến con trỏ n
*n = *n + 10;
}

int main()
{
/*
* Trường hợp biến thường
*/
int a = 5;

// Vì tham số của hàm XuLy là 1 con trỏ, bản chất con trỏ là địa chỉ
// Nên tham số truyền vào hàm XuLy cũng fai là 1 địa chỉ: &a
Xuly( &a );

printf("Gia tri cua a = %d\n", a);

/*
* Trường hợp biến con trỏ
*/
// Cấp phát vùng nhớ cho con trỏ
int *b = (int *)malloc( sizeof(int *) );

// Khởi tạo giá trị cho con trỏ
*b = 5;

// 2 dòng cấp phát và khởi tạo thì TƯƠNG ĐƯƠNG với 1 dòng trong C++ là:
// int *b = new int(5);

// b đã là 1 con trỏ rồi, nên khi truyền tham số không cần dấu & nữa.
Xuly( b );

printf("\nGia tri cua b = %d", *b);

// Giải phóng vùng nhớ đã cấp phát
free(b);

getchar();
return 0;
}
[/code]

 

Kết quả chương trình

Kết quả chương trình

■ Một số lưu ý ở phần này:
– Đối với tham số được truyền bằng con trỏ thì hàm vẫn cứ làm theo nguyên tắc đã nêu trên và 1 bản sao của con trỏ được tạo ra, và hàm làm việc với bản sao này, và trước khi gọi hàm con trỏ trỏ vào đâu thì nó vẫn được trỏ vào đấy.

[code language=”c” highlight=”6″] #include <stdio.h>
#include <conio.h>
#include <stdlib.h>

// Tham số truyền bằng con trỏ
void XuLy(int *a)
{
*a = 2;
a++;
}

int main()
{
// Khai báo và cấp phát vùng nhớ cho con trỏ
int *a = (int *)calloc( 1, sizeof(int *) );

printf("Dia chi Truoc : %x\n", a); // trước và sau khi gọi hàm

XuLy( a ); // con trỏ a trỏ vào đâu

printf("Dia chi Sau : %x\n", a); // thì nó vẫn trỏ vào đó

// Giải phóng vùng nhớ đã cấp phát
free( a );

getchar();
return 0;
}
[/code]

Địa chỉ của con trỏ Trước và Sau thực hiện hàm là Giống nhau: c70fd8

Địa chỉ của con trỏ Trước và Sau thực hiện hàm là Giống nhau: c70fd8

Vậy con trỏ ko thay đổi thì cái gì thay đổi được?
Đó chính là giá trị nằm trong vùng nhớ trỏ đến thay đổi. Do biến a của ta nằm trong vùng nhớ được trỏ đến nên nó được thay đổi.

・ Ví dụ 1:

[code language=”c” highlight=”5″] #include <stdio.h>
#include <conio.h>

// Tham số truyền bằng con trỏ
void XuLy(int *a)
{
*a = 2; // làm việc với địa chỉ nhận được
}

int main()
{
int a;

XuLy( &a ); // truyền địa chỉ của a vào cho hàm

printf("a = %d", a); // do đó sau hàm này a = 2

getchar();
return 0;
}
[/code]

 

・ Ví dụ 2:

[code language=”c” highlight=”6″] #include <stdio.h>
#include <conio.h>
#include <stdlib.h>

// Tham số truyền bằng con trỏ
void XuLy(int *a)
{
*a = 2; // làm việc với địa chỉ nhận được
}

int main()
{
// Khai báo và cấp phát vùng nhớ cho con trỏ
int *a = (int *)calloc(1, sizeof(int *));

printf("Truoc a = %d\n", *a);

XuLy( a ); // truyền địa chỉ của a vào cho hàm

printf("Sau a = %d\n", *a); // do đó sau hàm này a = 2

// Giải phóng vùng nhớ đã cấp phát
free(a);

getchar();
return 0;
}
[/code]

Giá trị của biến a được thay đổi

Giá trị của biến a được thay đổi

Tránh thao tác sai như sau:
Ban đầu con trỏ chưa trỏ đến đâu cả và được truyền vào hàm. Trong hàm chúng ta cấp phát bộ nhớ rồi cho bản sao đang làm việc trỏ đến. Sau đó ra khỏi hàm rồi thì con trỏ a của ta vẫn chưa có trỏ vào bộ nhớ nào cả, nên khi truy xuất vào giá trị của con trỏ a sẽ bị lỗi.

[code language=”c”] #include <stdio.h>
#include <stdlib.h>

void XuLy( int *a )
{
// Cấp phát bộ nhớ cho con trỏ bên trong hàm được gọi
// nên sẽ không liên quan gì đến vùng nhớ của con trỏ a ở hàm main
a = ( int * )calloc( 1, sizeof(int) );

*a = 2;

printf( "a = %d\n", *a ); // Output: a = 2
}

int main()
{
int *a = NULL;

XuLy( a );

// Kiểm tra con trỏ có NULL hay không
if ( a )
{
printf( "Not NULL" );
}
else
{
printf( "NULL" ); // Output: NULL
}

printf( "a = %d\n", *a ); // Xảy ra lỗi run-time,
// ném ra 1 exception vì con trỏ a là con trỏ null

getchar();
return 0;
}
[/code]

Vì truyền tham chiếu hay truyền con trỏ cho hàm đều làm thay đổi giá trị của biến tham số sau khi ra khỏi hàm. Do đó chúng ta có thể ứng dụng khi muốn lấy nhiều kết quả trả về từ 1 hàm. Ví dụ:

#include <iostream>

// Truyền tham chiếu
void TinhToan_1(int a, int b, int &Tong, int &Hieu, int &Tich)
{
	Tong = a + b;
	Hieu = a - b;
	Tich = a * b;
}

// Truyền con trỏ
// *Tong, *Hieu, *Tich: toán tử * ở đây biểu thị đây là 3 CON TRỎ Tong, Hieu Tich
void TinhToan_2(int a, int b, int *Tong, int *Hieu, int *Tich)
{
	// Bên trong hàm:
	// *Tong, *Hieu, *Tich: toán tử * dùng để lấy 3 GIÁ TRỊ của con trỏ Tong, Hieu Tich
	*Tong = a + b;
	*Hieu = a - b;
	*Tich = a * b;
}

int main()
{
	int a = 9;
	int b = 3;
	int Tong1, Hieu1, Tich1;
	int Tong2, Hieu2, Tich2;

	// Gọi hàm tính toán
	TinhToan_1( a, b, Tong1, Hieu1, Tich1 );
	TinhToan_2( a, b, &Tong2, &Hieu2, &Tich2 );

	// Lấy được 3 giá trị kết quả tổng, hiệu, tích
	printf("Tong cua hai so %d va %d la: %d, %d\n", a, b, Tong1, Tong2);
	printf("Hieu cua hai so %d va %d la: %d, %d\n", a, b, Hieu1, Hieu2);
	printf("Tich cua hai so %d va %d la: %d, %d\n", a, b, Tich1, Tich2);

	system( "pause" );
	return 0;
}
Kết quả chương trình

Kết quả chương trình

Cảm ơn bạn đã theo dõi. Đừng ngần ngại hãy cùng thảo luận với chúng tôi!

Giới thiệu

Minh Hoàng

Xin chào, tôi là Hoàng Ngọc Minh, hiện đang làm BrSE, tại công ty Toyota, Nhật Bản. Những gì tôi viết trên blog này là những trải nghiệm thực tế tôi đã đúc rút ra được trong cuộc sống, quá trình học tập và làm việc. Các bài viết được biên tập một cách chi tiết, linh hoạt để giúp bạn đọc có thể tiếp cận một cách dễ dàng nhất. Hi vọng nó sẽ có ích hoặc mang lại một góc nhìn khác cho bạn[...]

5 bình luận

Translate »