Contents
Con trỏ là gì?
Ví dụ 1: Nhập tên và in ra màn hình tên bạn vừa nhập
#include "stdio.h" #include "conio.h" void main() { char s[30]; printf("\nNhap ten: "); gets(s); printf("\nTen cua ban: %s", s); getch(); }
Ở đây, chương trình này có một số hạn chế như sau:
- Mảng kí tự s chứa tối đa được 29 kí tự
- Nếu bạn nhập tên có độ dài nhỏ hơn 29 gây lãng phí bộ nhớ
- Nếu bạn nhập tên có độ dài lớn hơn 29 gây lỗi chương trình
Để khắc phục những hạn chế trên đây, ngôn ngữ C đưa ra khái niệm biến con trỏ và cấp phát động
Vậy biến con trỏ là gì? Cấp phát động là gì?
Khái niệm: Biến con trỏ (Pointer variable) là biến chứa địa chỉ của biến hoặc chứa địa chỉ của ô nhớ.
Khai báo
<Kiểu dữ liệu> * <Tên biến con trỏ>
Kiểu dữ liệu: int, char, float, long, struct, … Kiểu dữ liệu nào thì sẽ lưu địa chỉ của biến có kiểu dữ liệu tương ứng
Tên biến con trỏ: đặt tên theo quy tắc đặt tên biến
Ví dụ 2:
#include "stdio.h" #include "conio.h" void main() { int *x; int a = 6; x = &a; printf("\na = %d", a); printf("\n&a = 0x%x", &a); printf("\nDia chi cua bien a: 0x%x", x); printf("\nGia tri cua bien a: %d", *x); printf("\nDia chi cua bien x: 0x%x", &x); getch(); }
Kết quả:
Đoạn chương trình trên khai báo biến con trỏ x kiểu nguyên. Lệnh x = &a, con trỏ x chứa địa chỉ của biến a và *x = a = 6. Minh họa như hình sau:
Ví dụ 3:
#include "stdio.h" #include "conio.h" void main() { int *x; float a = 1.6; x = &a; getch(); }
Kết quả:
Chương trình biên dịch báo lỗi vì con trỏ kiểu số nguyên không thể trỏ tới biến kiểu số thực.
Chú ý: Con trỏ chỉ có thể chứa địa chỉ của biến có cùng kiểu dữ liệu với con trỏ.
Phép toán thao tác con trỏ
Có 4 nhóm phép toán liên quan đến con trỏ và địa chỉ là: phép gán, phép tăng giảm địa chỉ, phép truy cập bộ nhớ và so sánh.
Phép gán
Chỉ nên thực hiện phép gán giữa các con trỏ cùng kiểu dữ liệu. Nếu muốn thực hiện phép gán giữa các con trỏ có kiểu dữ liệu khác nhau cần phải ép kiểu. Tuy nhiên, để thực hiện ép kiểu bạn phải hiểu rất rõ cách lưu địa chỉ của con trỏ.
Ví dụ 4:
#include "stdio.h" #include "conio.h" void main() { float *x, *y; float a = 1.6; x = &a; y = x; printf("\na = %f", a); printf("\n*x = %f", *x); printf("\n*y = %f", *y); printf("\n&a = 0x%x", &a); printf("\nx = 0x%x", x); printf("\ny = 0x%x", y); printf("\n&x = 0x%x", &x); printf("\n&y = 0x%x", &y); getch(); }
Kết quả:
Câu lệnh y = x tương đương y = x = &a. Lúc này, biến con trỏ y cũng chứa địa chỉ của biến a hay ta nói con trỏ y trỏ tới biến a. *x và *y chính là giá trị của biến a (*x = *y = a = 1.6). Hình sau mô tả địa chỉ và giá trị của biến a, biến con trỏ x, y.
Chú ý: Giá trị địa chỉ của các biến là số nguyên (kích thước 4 byte (Windows 32bit, Visual Studio 2008 Professional SP1)).
Phép tăng giảm địa chỉ
Dùng toán tử ++, —
Ví dụ 5:
#include "stdio.h" #include "conio.h" void main() { float A[]= {1.2, 2.5, 3.6, 4.8}; float *p; p = A; printf("\nGia tri *(p+0) = %f", *p); p++; printf("\nGia tri *(p+1) = %f", *p); getch(); }
Kết quả:
Khi khai báo 1 mảng thì tên mảng A chính là địa chỉ của phần tử đầu tiên của mảng; các phần tử của mảng int sẽ được lưu tại các ô nhớ liền kề nhau có kích thước 4 byte.
Câu lệnh gán p = A, con trỏ p trỏ tới phần tử đầu tiên của mảng và *p = A[0] = 1.2
Câu lệnh p++ tương đương với câu lệnh p = p+1 di chuyển con trỏ p tới 1×4(byte) (phần tử thứ 2 của mảng A) và *p = A[1] = 2.5.
Chú ý: Nếu p = p + k (k > 3), chương trình sẽ gây lỗi vì con trỏ p truy cập tới phần tử không xác định.
Hình sau minh họa hoạt động tăng giảm của biến con trỏ p. Việc thay đổi giá trị p chỉ làm thay đổi địa chỉ vùng nhớ mà con trỏ p trỏ tới mà không thay đổi địa chỉ của biến con trỏ p.
Ví dụ 6:
#include "stdio.h" #include "conio.h" void main() { char s[]= "vncoding"; char *p; p = s; printf("\n*p = %c", *p); printf("\np = 0x%x", p); printf("\n&p = 0x%x", &p); p = p + 4; printf("\n\n*p = %c", *p); printf("\np = 0x%x", p); printf("\n&p = 0x%x", &p); getch(); }
Kết quả:
Như bạn đã biết, khi khai báo 1 mảng thì tên mảng s chính là địa chỉ của phần tử đầu tiên của mảng; các phần tử của mảng char sẽ được lưu tại các ô nhớ liền kề nhau có kích thước 1 byte.
Câu lệnh gán p = s, con trỏ p trỏ tới phần tử đầu tiên của mảng và *p = s[0] = ‘v’
Câu lệnh p = p+4, di chuyển con trỏ p tới 4×1(byte) tiếp theo (phần tử thứ 5 tính từ phần tử ‘v’) và *p = s[4] = ‘d’
Nguyên tắc truy cập vùng nhớ
Con trỏ kiểu float truy cập 4 byte, kiểu int truy cập 4 byte, kiểu char truy cập 1 byte,..
float *a; int *b; char *c;
Giả sử cả 3 con trỏ a,b,c đang trỏ đến vùng nhớ 1500 thì:
*a sẽ biểu thị vùng nhớ 4 byte liên tiếp từ 1500 – 1503
*b sẽ biểu thị vùng nhớ 4 byte liên tiếp từ 1500 – 1503
*c sẽ biểu thì vùng nhớ 1 byte từ 1500 – 1501
Ví dụ 8:
#include "stdio.h" #include "conio.h" void main() { unsigned int n = 0xABCDEF;// hexa unsigned char *c; c = (unsigned char*)(&n); printf("\n&n = %d", &n); printf("\nc = %d", c); printf("\nc = %x", *c); c++; printf("\nc = %d", c); printf("\nc = %x", *c); c++; printf("\nc = %d", c); printf("\nc = %x", *c); getch(); }
Kết quả:
Giá trị n được lưu trên vùng nhớ như sau:
Kiểu số nguyên không dấu có kích thước 4 byte (windows 32bit). Vì con trỏ c là kiểu unsigned char, nên c truy cập 1 byte đầu tiên của n nên *c = 0xEF và *(c++) = 0xCD.
Phép so sánh 2 con trỏ
Cho phép so sánh 2 con trỏ p1,p2 cùng kiểu dữ liệu
p1 < p2 nếu địa chỉ p1 trỏ tới thấp hơn địa chỉ p2 trỏ tới
p1 = p2 nếu địa chỉ p1 trỏ tới bằng địa chỉ p2 trỏ tới
p1 > p2 nếu địa chỉ p1 trỏ tới cao hơn địa chỉ p2 trỏ tới
Mối liên hệ giữa con trỏ và mảng 1 chiều
Ví dụ 9:
#include "stdio.h" #include "conio.h" void main() { int A[] = {1, 2, 3, 4, 5}; int i; //duyet mang for(i = 0; i < 5; i++) printf("\nA[%d] = %d", i, *(A+i)); getch(); }
Kết quả:
Ghi nhớ: A là địa chỉ của phần tử đầu tiên của mảng A, A+1 : địa chỉ của phần tử thứ 2,…
Giá trị của các phần tử của mảng:
*(A+0) = A[0] = 1
*(A+1) = A[1] = 2
…
*(A+4) = A[4] = 5
Vậy : Muốn lấy giá trị của phần tử trong mảng 1 chiều ta có thể dùng: *(A+i) , i là chỉ số mảng.
Mối liên hệ giữa con trỏ và mảng 2 chiều
Ví dụ 10:
#include "stdio.h" #include "conio.h" #define N 4 #define M 3 void main() { int A[N][M] = {1, 2, 3, 4, 0, 5, 8, 9, 2, 3, 9, 3}; int i, j; for(i = 0; i < N; i++) for(j = 0; j < M; j++) { printf("\nA[%d][%d] = %d", i, j, *(*(A+i)+j)); printf("\nDia chi phan tu %d = %d", i+j, *(A+i)+j); } getch(); }
Kết quả:
Để lấy giá trị của các phần tử mảng 2 chiều ta dùng biểu thức sau: *(*(A+i)+j) và để lấy địa chỉ của phần tử mảng 2 chiều ta dùng biểu thức sau: *(A+i)+j. (trong đó i,j là chỉ số hàng và cột)
Great work. Thanks.