Lập trình C

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

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

Series lập trình 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)

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.

#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;
}
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.

#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;
}
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++)

■ Bài viết liên quan: Con trỏ trong lập trình C/C++

#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;
}
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.

#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;
}
Đị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:

#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;
}

・ Ví dụ 2:

#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;
}
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.

#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;
}

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! Minh Hoàng Blog | Nào cùng vui hehe


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 người đọ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[…]

Bình luận của bạn

3 Comments cho bài viết "Tham trị, tham chiếu trong lập trình C/C++"

avatar
Sắp xếp theo:   Mới nhất | Cũ nhất | Thích nhiều nhất
trackback

[…] Cách truyền tham trị, tham chiếu trong lập trình C […]

trackback

[…] code và tránh trùng lặp. Để làm được điều này thì sau khi đọc bài viết Cách truyền tham trị, tham chiếu trong lập trình C này các bạn làm thử xem sao […]

wpDiscuz
Chúc bạn có một cuộc sống ngoại hạng!