Con trỏ là một kiến thức quan trọng trong ngôn ngữ lập trình C/C++, bài viết này sẽ giúp bạn hiểu rõ bản chất của con trỏ và cách sử dụng nó.
1. Con Trỏ Và Địa Chỉ
Con trỏ hay biến con trỏ cũng là một biến thông thường nhưng giá trị mà nó lưu lại là địa chỉ của 1 biến khác.
Ví dụ biến kiểu int N trong chương trình sẽ có địa chỉ nhất định trong bộ nhớ, để lưu trữ giá trị địa chỉ này ta cần biến con trỏ kiểu int
Khi khai báo biến con trỏ ta thêm dấu * vào trước tên biến.
Cú pháp khai báo : Kiểu_Dữ_Liệu *Tên_Biến_Con_Trỏ;
Ví dụ :
#include <math.h>
// Dấu * thể hiện ptr là con trỏ
int *ptr; // con trỏ kiểu int
//Dấu * có thể đặt cạnh tên biến hoặc cạnh kiểu dữ liệu
long long* ptr2; // con trỏ kiểu long long
char *ptr3;
return 0;
}
Mỗi biến trong chương trình đều được cấp phát vùng nhớ để lưu trữ giá trị của nó, ví dụ biến int sẽ được cấp phát 4 byte liên tiếp để lưu trữ và lấy địa chỉ của byte đầu tiên làm địa chỉ cho biến.
Để in ra địa chỉ của biến bạn dùng toán tử &, ví dụ &N sẽ cho bạn địa chỉ của biến N
Code :
#include <math.h>
int N = 28;
long long M = 10000012828;
cout << "Dia chi cua N trong bo nho : " << &N << endl;
cout << "Dia chi cua M trong bo nho : " << &M << endl;
return 0;
}
Output :
Dia chi cua M trong bo nho : 0x6ffe18
Chú ý : Khi bạn chạy code trên thì địa chỉ của N và M sẽ khác, và mỗi lần bạn chạy có thể sẽ có một địa chỉ khác nhau.
Con trỏ được sinh ra để lưu địa chỉ của biến ở kiểu dữ liệu tương ứng với nó, ví dụ biến con trỏ kiểu int sẽ lưu được địa chỉ của biến int.
Chương trình sau sẽ gán địa chỉ của N cho biến con trỏ ptr, khi đó ta nói con trỏ ptr trỏ tới biến N
Code :
#include <math.h>
int N = 1000;
cout << "Dia chi cua N : " << &N << endl;
int *ptr;
// Gán địa chỉ của N cho ptr
ptr = &N;
cout << "Gia tri cua ptr : " << ptr << endl;
return 0;
}
Output :
Gia tri cua ptr : 0x6ffe1c
2. Tham Chiếu Và Giải Tham Chiếu
Khi con trỏ ptr trỏ tới hay tham chiếu (reference) tới biến N thì thông qua con trỏ ptr ta có thể truy xuất, thay đổi giá trị của biến N mà không cần dùng N.
Để truy xuất tới giá trị của biến mà con trỏ đang trỏ tới ta dùng toán tử giải tham chiếu * (dereference)
Sau khi con trỏ ptr trỏ tới biến N thì N và *ptr là một, đều truy xuất đến ô nhớ mà N đang chiếm để lấy giá trị tại ô nhớ đó.
Lưu ý là bạn cần phân biệt dấu * khi khai báo con trỏ ptr và dấu * khi giải tham chiếu con trỏ ptr. Dấu * khi khai báo thể hiện ptr là một con trỏ còn dấu * trước ptr ở những câu lệnh sau là toán tử giải tham chiếu.
#include <math.h>
int N = 1000;
cout << "Dia chi cua N : " << &N << endl;
int *ptr = &N; // ptr trỏ tới N
cout << "Gia tri cua ptr : " << ptr << endl;
// Toán tử giải tham chiếu
cout << "Gia tri cua bien ma con tro ptr tro toi : " << *ptr << endl;
// Thay đổi N bằng ptr, *ptr và N là một
*ptr = 280;
cout << "Gia tri cua N sau thay doi : " << N << " " << *ptr << endl;
return 0;
}
Output :
Gia tri cua ptr : 0x6ffe1c
Gia tri cua bien ma con tro ptr tro toi : 1000
Gia tri cua N sau thay doi : 280 280
Một biến có thể được trỏ tới bởi nhiều con trỏ, khi đó bạn có thể thông qua bất cứ 1 con trỏ nào để thay đổi giá trị của biến mà nó đang trỏ tới.
Trong ví dụ dưới đây 3 biến con trỏ ptr1, ptr2, ptr3 đều trỏ tới N nên *ptr1, *ptr2, *ptr3 và N đều có giá trị giống nhau.
Code :
#include <math.h>
int N = 1000;
int *ptr1 = &N; // ptr1 trỏ tới N
int *ptr2 = &N; // ptr2 trỏ tới N
int *ptr3 = ptr1; // Gán giá trị của ptr1 cho ptr3, tương tự gán &N cho ptr3
cout << "Gia tri cua 3 con tro : " << ptr1 << " " << ptr2 << " " << ptr3 << endl;
*ptr1 = 100; // N = 100
cout << "Gia tri cua N : " << N << endl;
*ptr2 = 200; // N = 200
cout << "Gia tri cua N : " << N << endl;
*ptr3 = 300; // N = 300
cout << *ptr1 << " " << *ptr2 << " " << *ptr3 << " " << N << endl;
return 0;
}
Output :
Gia tri cua N : 100
Gia tri cua N : 200
300 300 300 300
3. Hàm Và Con Trỏ
Con trỏ làm tham số cho hàm
Để thay đổi giá trị của 1 biến sau khi hàm kết thúc thì việc truyền giá trị là không hợp lý, thay vì đó bạn hãy sử dụng con trỏ với mục đích là thay đổi giá trị của biến thông qua con trỏ.
Khi hàm có tham số là một con trỏ thì khi gọi hàm bạn cần truyền vào một giá trị phù hợp, có thể là địa chỉ của 1 biến hoặc một con trỏ khác.
Ví dụ 1: Thay đổi giá trị của biến sau khi hàm kết thúc
Code :
#include <math.h>
void change(int *x) {
cout << "Gia tri cua con tro x : " << x << endl;
cout << "Gia tri cua bien ma x đang tro toi : " << *x << endl;
//Đây là tham chiếu tới giá trị của biến
//mà con trỏ x đang trỏ tới để thay đổi nó thành 1000
*x = 1000;
}
int N = 28;
cout << "Dia chi cua N : " << &N << endl;
change(&N); // truyền địa chỉ của N vào
cout << "Dia chi cua N : " << N << endl;
return 0;
}
Output :
Gia tri cua con tro x : 0x6ffe1c
Gia tri cua bien ma x đang tro toi : 28
Gia tri cua N : 1000
Giải thích :
Hàm change có tham số là một con trỏ kiểu int có tên là x, trong main bạn gọi change và truyền địa chỉ của N vào.
Khi đó x sẽ được gán giá trị là địa chỉ của N, trong hàm change thì câu lệnh *x = 1000 sẽ truy xuất tới ô nhớ mà x đang trỏ tới và gán giá trị 1000. Mà x lại đang tham chiếu tới N nên giá trị của N đã bị thay đổi.
Sau khi hàm change kết thúc thì giá trị của N đã bị thay đổi thực sự.
Ví dụ 2 : Hoán đổi giá trị của 2 biến
#include <math.h>
int tmp = *x;
*x = *y;
*y = tmp;
}
int a = 100, b = 200;
my_swap(&a, &b);
cout << a << " " << b << endl;
return 0;
}
Output :
Ví dụ 3 : Hàm trả về con trỏ
#include <math.h>
x = y;
return x;
}
int N = 28, M = 56;
int *ptr1 = &N;
int *ptr2 = &M;
cout << "Truoc khi goi ham : \n";
cout << "Gia tri cua ptr1 : " << ptr1 << endl;
cout << "Gia tri cua ptr2 : " << ptr2 << endl;
ptr1 = convert(ptr1, ptr2);
cout << "Sau khi goi ham : \n";
cout << "Gia tri cua ptr1 : " << ptr1 << endl;
cout << "Gia tri cua ptr2 : " << ptr2 << endl;
return 0;
}
Output :
Gia tri cua ptr1 : 0x6ffe18
Gia tri cua ptr2 : 0x6ffe1c
Sau khi goi ham :
Gia tri cua ptr1 : 0x6ffe1c
Gia tri cua ptr2 :0x6ffe1c