Trong C++, có một khái niệm rất hay nhưng khá khó hiểu khi mới lập trình là con trỏ (pointer). Để tìm hiểu về con trỏ, các bạn cần nắm vững về bộ nhớ của biến trong lập trình. Các bạn có thể đọc lại bài Hiểu rõ về bộ nhớ của biến trong C++.
1. Khái niệm con trỏ
Nhớ lại bộ nhớ lưu trữ biến
Bộ nhớ RAM chứa rất nhiều ô nhớ, mỗi ô nhớ có kích thước 1 byte.
Mỗi ô nhớ có địa chỉ duy nhất và địa chỉ này được đánh số từ 0 trở đi. Nếu CPU 32 bit thì có 2^32 địa chỉ có thể đánh cho các ô nhớ trong RAM.
Khi khai báo biến, trình biên dịch dành riêng một vùng nhớ với địa chỉ duy nhất để lưu biến. Trình biên dịch có nhiệm vụ liên kết địa chỉ ô nhớ đó với tên biến. Khi gọi tên biến, nó sẽ truy xuất tự động đến ô nhớ đã liên kết với tên biến để lấy dữ liệu. Các bạn phải luôn phân biệt giữa địa chỉ bộ nhớ và dữ liệu được lưu trong đó.
Có thể lấy địa chỉ của một biến bằng cách sử dụng toán tử &.
int a;
cout << "Address of a: " << &a << endl;
Address of a: 00F6FDFC
Địa chỉ của biến bản chất cũng là một con số thường được biểu diễn ở hệ cơ số 16. Ta có thể sử dụng con trỏ (pointer) để lưu địa chỉ của các biến.
Con trỏ là gì?
Trong ngôn ngữ C++, con trỏ (pointer) là những biến lưu trữ địa chỉ bộ nhớ của những biến khác.
Trong hình trên, biến var lưu giá trị 5 có địa chỉ là 0x61ff08. Biến pointVar là biến con trỏ, lưu địa chỉ của biến var (trỏ đến vùng nhớ của biến var), tức là nó lưu giá trị 0x61ff08.
2. Khai báo và khởi tạo biến con trỏ
Cú pháp khai báo biến con trỏ
<kiểu dữ liệu> *<tên biến con trỏ>;
Ví dụ:
char *ch1, *ch2;
int *p1, p2;
ch1 và ch2 là biến con trỏ, trỏ tới vùng nhớ kiểu char (1 byte). p1 là biến con trỏ, trỏ tới vùng nhớ kiểu int (4 bytes), còn p2 là biến kiểu int bình thường.
Khởi tạo biến con trỏ
Một biến bất kỳ phải xác định 2 thứ: địa chỉ của biến và giá trị của biến. Biến con trỏ cũng thế.
Khi mới khai báo, biến con trỏ được đặt ở địa chỉ nào đó (không biết trước), chứa giá trị là một địa chỉ không xác định hoặc địa chỉ 0xCCCCCCCC – là địa chỉ đặc biệt, cho biết con trỏ chưa được khởi tạo. Sử dụng toán tử & để đặt địa chỉ của một biến vào con trỏ.
Cú pháp: <tên biến con trỏ> = &<tên biến>;
Ví dụ:
int a, b;
int *pa, *pb;
pa = &a;
pb = &b;
Con trỏ NULL
Con trỏ NULL là con trỏ lưu địa chỉ 0x00000000. Tức địa chỉ bộ nhớ 0, có ý nghĩa đặc biệt, cho biết con trỏ không trỏ vào đâu cả.
int *p2;//con trỏ chưa khởi tạo, vẫn trỏ đến một vùng nhớ nào đó không xác định
int *p3 = NULL;//con trỏ null không trỏ đến vùng nhớ nào
3. Sử dụng biến con trỏ
Ví dụ có các khai báo:
int a = 5;
int *pa = &a;
Nắm rõ quy tắc sau: *pa và a đều chỉ giá trị của biến a, pa và &a đều chỉ địa chỉ của biến a, &pa là lấy địa chỉ của biến con trỏ pa.
#include <iostream>
using namespace std;
int main() {
int a = 5;
int *pa;
pa = &a;
//Dia chi
cout<<"Gia tri cua bien con tro pa la dia chi cua bien a:"<<pa<<endl;
cout<<"Dia chi cua bien a ma bien con tro pa tro den:"<<&a<<endl;
//Gia tri
cout<<"Gia tri vung nho ma bien con tro pa tro den:"<<*pa<<endl;
cout<<"Gia tri cua bien a ma bien con tro pa tro den:"<<a<<endl;
//Dia chi bien con tro
cout<<"Dia chi cua bien con tro pa:"<<&pa<<endl;
system("pause");
}
Kết quả
Gia tri cua bien con tro pa la dia chi cua bien a:00AFF948
Dia chi cua bien a ma bien con tro pa tro den:00AFF948
Gia tri vung nho ma bien con tro pa tro den:5
Gia tri cua bien a ma bien con tro pa tro den:5
Dia chi cua bien con tro pa:00AFF93C
4. Kích thước của con trỏ
Ví dụ các khai báo con trỏ sau:
char *p1;
int *p2;
float *p3;
double *p4;
Kích thước của các biến con trỏ có khác nhau không? Con trỏ chỉ lưu địa chỉ nên kích thước của mọi con trỏ là như nhau. Kích thước này phụ thuộc vào môi trường hệ thống máy tính:
- Môi trường MS-DOS 16 bit: 2 bytes
- Môi trường Windows 32 bit: 4 bytes
- Môi trường Windows 64 bit: 8 bytes
Chương trình xem kích thước của con trỏ
#include <iostream>
using namespace std;
int main() {
char *p1;
int *p2;
float *p3;
double *p4;
cout<<"Size of char type pointer:"<<sizeof(char *)<<" bytes" <<endl;
cout<<"Size of int type pointer:"<<sizeof(int *)<<" bytes" <<endl;
cout<<"Size of float type pointer:"<<sizeof(float *)<<" bytes" <<endl;
cout<<"Size of double type pointer:"<<sizeof(double *)<<" bytes" <<endl;
system("pause");
}
Kết quả trên Windows 64 bit
Size of char type pointer:8 bytes
Size of int type pointer:8 bytes
Size of float type pointer:8 bytes
Size of double type pointer:8 bytes
5. Một số lưu ý khi sử dụng con trỏ
Khi khởi tạo con trỏ NULL
Chữ NULL phải viết hoa, viết thường null sẽ bị lỗi.
int *p1 = NULL;//đúng
int *p2 = null;//lỗi
Không nên sử dụng con trỏ khi chưa được khởi tạo
Kết quả tính toán có thể sẽ phát sinh những lỗi không lường trước được nếu chưa khởi tạo con trỏ.
int a = 5, *pa;
pa = &a;//được khởi tạo
int *pb;//phải khởi tạo
Sử dụng biến con trỏ sai cách
int var, *varPoint;
// lỗi
// varPoint là địa chỉ nhưng var không phải địa chỉ
varPoint = var;
// lỗi
// &var là địa chỉ
// *varPoint là giá trị được lưu trữ trong var
*varPoint = &var;
// đúng
// varPoint là địa chỉ và &var cũng là địa chỉ
varPoint = &var;
// đúng
// *varPoint và var đều là giá trị
*varPoint = var;
Khi thay đổi giá trị trong vùng nhớ
Xem ví dụ sau:
#include <iostream>
using namespace std;
int main() {
int *pa;
int a=5;
pa=&a;//pa lưu địa chỉ của biến a
*pa=100;//thay đổi giá trị trong vùng nhớ mà pa lưu trữ
cout<<"Gia tri cua a:"<<a;//giá trị lưu trong a cũng thay đổi
system("pause");
}
Kết quả
Gia tri cua a:100
Tên biến chỉ đại diện cho vùng nhớ, có thể truy xuất hoặc thay đổi giá trị trong vùng nhớ đó. Do đó, khi giá trị trong vùng nhớ thay đổi thì giá trị truy xuất được thông qua tên biến cũng thay đổi.
Con trỏ là khái niệm quan trọng và khó nhất trong C/C++. Mức độ thành thạo C/C++ được đánh giá qua mức độ sử dụng con trỏ. Do đó, các bạn cố gắng hiểu rõ và luyện tập cách sử dụng con trỏ nhé.
Nếu có bất cứ thắc mắc nào, các bạn cứ comment bên dưới, mình sẽ giải đáp cho các bạn.