1. Con trỏ cấp 2 (Pointer to Pointer / Double Pointer)
Biến lưu địa chỉ của một con trỏ khác. Ký hiệu: **.
Khai báo và khởi tạo
int a = 10;
int *p = &a; // p trỏ đến a
int **q = &p; // q trỏ đến p (lưu địa chỉ của p)
Truy xuất giá trị
a // 10
*p // 10 (giá trị tại địa chỉ p)
**q // 10 (giá trị tại địa chỉ q trỏ đến -> p -> a)
*q // địa chỉ của a (giá trị của p)
q // địa chỉ của p
Chương trình minh họa
#include <stdio.h>
int main() {
int a = 10;
int *p = &a;
int **q = &p;
printf("a = %d\n", a);
printf("*p = %d\n", *p);
printf("**q = %d\n", **q);
printf("\n");
printf("p = %p\n", (void*)p);
printf("*q = %p\n", (void*)*q);
printf("q = %p\n", (void*)q);
// Sửa giá trị qua con trỏ cấp 2
**q = 20;
printf("\nSau **q = 20: a = %d\n", a);
return 0;
}
Kết quả
a = 10
*p = 10
**q = 10
p = 0x7ffc1234
*q = 0x7ffc1234
q = 0x7ffc1228
Sau **q = 20: a = 20
Ứng dụng chính của Pointer to Pointer
1. Truyền con trỏ vào hàm để cấp phát/đổi địa chỉ
void capPhatMang(int **arr, int n) {
*arr = malloc(n * sizeof(int)); // Sửa địa chỉ arr trong main
}
int main() {
int *a = NULL;
capPhatMang(&a, 10); // Truyền địa chỉ con trỏ
// a giờ trỏ đến vùng nhớ mới
free(a);
}
2. Mảng con trỏ (Array of Pointers) - Chuỗi độ dài khác nhau
char *ten[] = {"An", "Binh", "Cuong"}; // Mảng con trỏ char
// ten[0] -> "An", ten[1] -> "Binh"...
3. Ma trận động (Jagged array)
int **mt = malloc(3 * sizeof(int*));
for (int i = 0; i < 3; i++) {
mt[i] = malloc((i+1) * sizeof(int)); // Hàng i có i+1 cột
}
4. Danh sách liên kết - Thay đổi head trong hàm
void themDau(Node **head, int data) {
Node *moi = malloc(sizeof(Node));
moi->data = data;
moi->next = *head;
*head = moi; // Cập nhật head của main
}
2. Con trỏ void (Void Pointer / Generic Pointer)
Con trỏ không kiểu (void*), có thể trỏ đến bất kỳ kiểu dữ liệu nào. Kích thước bằng con trỏ thường (4/8 bytes).
Khai báo
void *vp;
int a = 10;
float f = 3.14;
vp = &a; // OK
vp = &f; // OK
Truy xuất: PHẢI ÉP KIỂU (Type Cast)
int a = 10;
void *vp = &a;
printf("%d\n", *(int*)vp); // Ép vp thành int* rồi dereference
Chương trình demo
#include <stdio.h>
void inGiaTri(void *ptr, char kieu) {
switch (kieu) {
case 'i': printf("int: %d\n", *(int*)ptr); break;
case 'f': printf("float: %.2f\n", *(float*)ptr); break;
case 'c': printf("char: %c\n", *(char*)ptr); break;
case 'd': printf("double: %f\n", *(double*)ptr); break;
default: printf("Kieu khong ho tro\n");
}
}
int main() {
int a = 42;
float b = 3.14f;
char c = 'Z';
double d = 2.71828;
inGiaTri(&a, 'i');
inGiaTri(&b, 'f');
inGiaTri(&c, 'c');
inGiaTri(&d, 'd');
return 0;
}
Kết quả
int: 42
float: 3.14
char: Z
double: 2.718280
Ứng dụng Void Pointer
| Ứng dụng | Ví dụ |
|---|---|
| Hàm generic (qsort, bsearch) | void qsort(void *base, size_t n, size_t size, int (*cmp)(const void*, const void*)) |
| Cấp phát bộ nhớ | malloc(), calloc(), realloc() trả về void* |
| Callback/Function pointer | Truyền context dữ liệu bất kỳ |
| Container/Generic data structure | Stack, Queue, List lưu void* |
Ví dụ: qsort với void pointer
#include <stdio.h>
#include <stdlib.h>
int soSanhInt(const void *a, const void *b) {
return (*(int*)a - *(int*)b);
}
int main() {
int a[] = {5, 2, 8, 1, 9};
int n = 5;
qsort(a, n, sizeof(int), soSanhInt);
printf("Mang sap xep: ");
for (int i = 0; i < n; i++) printf("%d ", a[i]);
printf("\n");
return 0;
}
Lưu ý quan trọng
- Không thể dereference
void*trực tiếp:*vp→ LỖI - Không thể pointer arithmetic:
vp + 1→ LỖI (không biết kích thước kiểu) - Phải ép kiểu đúng:
(int*)vp,(float*)vp... Sai kiểu = Undefined Behavior - C++:
void*tự chuyển ngầm sang kiểu khác. C: Cần cast tường minh