Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Lập Trình C

Con trỏ cấp 2 (Pointer to Pointer) và Con trỏ void trong C

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 pointerTruyền context dữ liệu bất kỳ
Container/Generic data structureStack, 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