Con trỏ trong C: Chìa khóa quản lý bộ nhớ và tối ưu hiệu suất

banner

Tóm tắt kiến thức

Con trỏ là khái niệm mạnh mẽ nhất trong C, cho phép trực tiếp thao tác với bộ nhớ và tạo ra các chương trình hiệu quả. Hiểu rõ con trỏ là chìa khóa để làm chủ C và viết code chuyên nghiệp.

Con trỏ là một trong những khái niệm quan trọng và mạnh mẽ nhất trong ngôn ngữ lập trình C. Con trỏ cho phép trực tiếp truy cập và thao tác với bộ nhớ, giúp tạo ra các chương trình hiệu quả, linh hoạt và tối ưu về mặt hiệu suất.

Quảng cáo giúp chúng tôi duy trì trang web này

Tổng quan về con trỏ

Khái niệm con trỏ

Con trỏ là một biến lưu trữ địa chỉ của biến khác trong bộ nhớ. Con trỏ cho phép truy cập gián tiếp đến dữ liệu thông qua địa chỉ của nó.

Cú pháp khai báo con trỏ

Cú pháp khai báo con trỏ
kiểu_dữ_liệu *tên_con_trỏ;

Ví dụ thực tế:

Ví dụ con trỏ cơ bản
#include <stdio.h>

int main() {
    int number = 42;
    int *ptr = &number;  // ptr trỏ đến địa chỉ của number

    printf("Gia tri cua number: %d\n", number);
    printf("Dia chi cua number: %p\n", &number);
    printf("Gia tri cua ptr (dia chi): %p\n", ptr);
    printf("Gia tri tai dia chi ptr tro toi: %d\n", *ptr);

    return 0;
}
Tại sao con trỏ quan trọng?
  • Quản lý bộ nhớ: Cấp phát và giải phóng bộ nhớ động
  • Hiệu suất cao: Truyền địa chỉ thay vì sao chép dữ liệu
  • Linh hoạt: Thao tác với mảng, chuỗi và cấu trúc
  • Hàm trả về nhiều giá trị: Thông qua tham chiếu

Toán tử con trỏ

Toán tử & (Address of)

#include <stdio.h>

int main() {
    int a = 10;
    char ch = 'A';
    float f = 3.14;

    printf("Dia chi cua a: %p\n", &a);
    printf("Dia chi cua ch: %p\n", &ch);
    printf("Dia chi cua f: %p\n", &f);

    return 0;
}

Toán tử * (Dereference)

#include <stdio.h>

int main() {
    int value = 100;
    int *ptr = &value;

    printf("Gia tri cua value: %d\n", value);
    printf("Gia tri tai dia chi ptr: %d\n", *ptr);

    // Thay đổi giá trị thông qua con trỏ
    *ptr = 200;

    printf("Gia tri cua value sau khi thay doi: %d\n", value);

    return 0;
}

Con trỏ và mảng

Mối quan hệ giữa con trỏ và mảng

#include <stdio.h>

int main() {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr = arr;  // ptr trỏ đến phần tử đầu tiên

    printf("Mang ban dau: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    printf("Truy cap qua con tro: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", *(ptr + i));
    }
    printf("\n");

    printf("Truy cap qua dia chi mang: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", *(arr + i));
    }
    printf("\n");

    return 0;
}

Con trỏ trỏ đến mảng

#include <stdio.h>

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    int (*ptr)[5] = &arr;  // Con trỏ trỏ đến mảng 5 phần tử

    printf("Gia tri cac phan tu qua con tro mang:\n");
    for (int i = 0; i < 5; i++) {
        printf("arr[%d] = %d\n", i, (*ptr)[i]);
    }

    return 0;
}

Con trỏ với chuỗi

#include <stdio.h>

int main() {
    char str[] = "Hello World";
    char *ptr = str;

    printf("Chuoi ban dau: %s\n", str);
    printf("Chuoi qua con tro: %s\n", ptr);

    printf("Cac ky tu qua con tro:\n");
    while (*ptr != '\0') {
        printf("%c ", *ptr);
        ptr++;
    }
    printf("\n");

    return 0;
}

Con trỏ với struct

#include <stdio.h>
#include <string.h>

typedef struct {
    char name[50];
    int age;
    float salary;
} Employee;

int main() {
    Employee emp = {"Nguyen Van A", 30, 15000000};
    Employee *ptr = &emp;

    // Truy cập thành phần qua con trỏ
    printf("Ten: %s\n", ptr->name);
    printf("Tuoi: %d\n", ptr->age);
    printf("Luong: %.0f\n", ptr->salary);

    // Thay đổi giá trị qua con trỏ
    ptr->age = 31;
    ptr->salary = 16000000;

    printf("\nSau khi thay doi:\n");
    printf("Tuoi: %d\n", ptr->age);
    printf("Luong: %.0f\n", ptr->salary);

    return 0;
}

Con trỏ hàm (Function Pointer)

Khai báo và sử dụng con trỏ hàm

#include <stdio.h>

int add(int a, int b) {
    return a + b;
}

int subtract(int a, int b) {
    return a - b;
}

int multiply(int a, int b) {
    return a * b;
}

int main() {
    int (*operation)(int, int);  // Con trỏ hàm

    int x = 10, y = 5;

    // Trỏ đến hàm add
    operation = add;
    printf("%d + %d = %d\n", x, y, operation(x, y));

    // Trỏ đến hàm subtract
    operation = subtract;
    printf("%d - %d = %d\n", x, y, operation(x, y));

    // Trỏ đến hàm multiply
    operation = multiply;
    printf("%d * %d = %d\n", x, y, operation(x, y));

    return 0;
}

Con trỏ hàm trong mảng

#include <stdio.h>

int add(int a, int b) { return a + b; }
int subtract(int a, int b) { return a - b; }
int multiply(int a, int b) { return a * b; }
int divide(int a, int b) { return (b != 0) ? a / b : 0; }

int main() {
    int (*operations[])(int, int) = {add, subtract, multiply, divide};
    char *operationNames[] = {"Cong", "Tru", "Nhan", "Chia"};

    int a = 20, b = 4;

    for (int i = 0; i < 4; i++) {
        printf("%s: %d %c %d = %d\n",
               operationNames[i], a,
               (i == 0) ? '+' : (i == 1) ? '-' : (i == 2) ? '*' : '/',
               b, operations[i](a, b));
    }

    return 0;
}

Cấp phát bộ nhớ động

malloc() - Cấp phát bộ nhớ

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    printf("Nhap kich thuoc mang: ");
    scanf("%d", &n);

    // Cấp phát bộ nhớ cho mảng
    int *arr = (int*)malloc(n * sizeof(int));

    if (arr == NULL) {
        printf("Khong the cap phat bo nho!\n");
        return 1;
    }

    // Nhập mảng
    printf("Nhap %d phan tu:\n", n);
    for (int i = 0; i < n; i++) {
        printf("arr[%d] = ", i);
        scanf("%d", &arr[i]);
    }

    // Hiển thị mảng
    printf("Mang vua nhap: ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // Giải phóng bộ nhớ
    free(arr);

    return 0;
}

calloc() - Cấp phát và khởi tạo về 0

#include <stdio.h>
#include <stdlib.h>

int main() {
    int n;
    printf("Nhap kich thuoc mang: ");
    scanf("%d", &n);

    // Cấp phát và khởi tạo về 0
    int *arr = (int*)calloc(n, sizeof(int));

    if (arr == NULL) {
        printf("Khong the cap phat bo nho!\n");
        return 1;
    }

    printf("Mang sau khi khoi tao (calloc): ");
    for (int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    free(arr);
    return 0;
}

realloc() - Thay đổi kích thước bộ nhớ

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *arr = (int*)malloc(3 * sizeof(int));

    if (arr == NULL) {
        printf("Khong the cap phat bo nho!\n");
        return 1;
    }

    // Khởi tạo mảng ban đầu
    arr[0] = 1;
    arr[1] = 2;
    arr[2] = 3;

    printf("Mang ban dau: ");
    for (int i = 0; i < 3; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    // Thay đổi kích thước thành 5 phần tử
    arr = (int*)realloc(arr, 5 * sizeof(int));

    if (arr == NULL) {
        printf("Khong the thay doi kich thuoc!\n");
        return 1;
    }

    // Thêm phần tử mới
    arr[3] = 4;
    arr[4] = 5;

    printf("Mang sau khi thay doi kich thuoc: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");

    free(arr);
    return 0;
}

Con trỏ với mảng động 2D

#include <stdio.h>
#include <stdlib.h>

int main() {
    int rows, cols;

    printf("Nhap so hang: ");
    scanf("%d", &rows);
    printf("Nhap so cot: ");
    scanf("%d", &cols);

    // Cấp phát mảng 2D động
    int **matrix = (int**)malloc(rows * sizeof(int*));

    for (int i = 0; i < rows; i++) {
        matrix[i] = (int*)malloc(cols * sizeof(int));
    }

    // Nhập ma trận
    printf("Nhap ma trận %dx%d:\n", rows, cols);
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("matrix[%d][%d] = ", i, j);
            scanf("%d", &matrix[i][j]);
        }
    }

    // Hiển thị ma trận
    printf("Ma trận:\n");
    for (int i = 0; i < rows; i++) {
        for (int j = 0; j < cols; j++) {
            printf("%d\t", matrix[i][j]);
        }
        printf("\n");
    }

    // Giải phóng bộ nhớ
    for (int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);

    return 0;
}

Con trỏ cấp độ cao

Con trỏ đến con trỏ

#include <stdio.h>

int main() {
    int value = 100;
    int *ptr1 = &value;
    int **ptr2 = &ptr1;

    printf("Gia tri cua value: %d\n", value);
    printf("Gia tri tai ptr1: %d\n", *ptr1);
    printf("Gia tri tai ptr2: %d\n", **ptr2);

    printf("Dia chi cua value: %p\n", &value);
    printf("Dia chi trong ptr1: %p\n", ptr1);
    printf("Dia chi trong ptr2: %p\n", *ptr2);

    // Thay đổi giá trị qua con trỏ cấp 2
    **ptr2 = 200;
    printf("Gia tri sau khi thay doi: %d\n", value);

    return 0;
}

Con trỏ NULL và kiểm tra lỗi

#include <stdio.h>
#include <stdlib.h>

int main() {
    int *ptr = NULL;

    // Kiểm tra con trỏ NULL
    if (ptr == NULL) {
        printf("Con tro chua duoc khoi tao!\n");
    }

    // Cấp phát bộ nhớ
    ptr = (int*)malloc(sizeof(int));

    if (ptr == NULL) {
        printf("Khong the cap phat bo nho!\n");
        return 1;
    }

    *ptr = 42;
    printf("Gia tri: %d\n", *ptr);

    free(ptr);
    ptr = NULL;  // Đặt con trỏ về NULL sau khi giải phóng

    return 0;
}

Ví dụ thực hành

1. Hàm tìm min/max trong mảng bằng con trỏ

#include <stdio.h>
#include <stdlib.h>

void findMinMax(int *arr, int size, int *min, int *max) {
    *min = *max = arr[0];

    for (int i = 1; i < size; i++) {
        if (arr[i] < *min) {
            *min = arr[i];
        }
        if (arr[i] > *max) {
            *max = arr[i];
        }
    }
}

int main() {
    int n;
    printf("Nhap kich thuoc mang: ");
    scanf("%d", &n);

    int *arr = (int*)malloc(n * sizeof(int));

    printf("Nhap %d phan tu:\n", n);
    for (int i = 0; i < n; i++) {
        scanf("%d", &arr[i]);
    }

    int min, max;
    findMinMax(arr, n, &min, &max);

    printf("So nho nhat: %d\n", min);
    printf("So lon nhat: %d\n", max);

    free(arr);
    return 0;
}

2. Tạo mảng động có kích thước theo yêu cầu

#include <stdio.h>
#include <stdlib.h>

int* createDynamicArray(int size) {
    int *arr = (int*)calloc(size, sizeof(int));

    if (arr == NULL) {
        printf("Khong the cap phat bo nho!\n");
        return NULL;
    }

    return arr;
}

void fillArray(int *arr, int size) {
    printf("Nhap %d phan tu:\n", size);
    for (int i = 0; i < size; i++) {
        printf("arr[%d] = ", i);
        scanf("%d", &arr[i]);
    }
}

void printArray(int *arr, int size) {
    printf("Mang: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int size;
    printf("Nhap kich thuoc mang: ");
    scanf("%d", &size);

    int *arr = createDynamicArray(size);

    if (arr != NULL) {
        fillArray(arr, size);
        printArray(arr, size);
        free(arr);
    }

    return 0;
}

3. Đếm tần suất nguyên âm trong chuỗi

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

void countVowels(char *str, int *vowelCount) {
    int len = strlen(str);

    // Khởi tạo mảng đếm
    for (int i = 0; i < 5; i++) {
        vowelCount[i] = 0;
    }

    for (int i = 0; i < len; i++) {
        char ch = tolower(str[i]);

        switch (ch) {
            case 'a': vowelCount[0]++; break;
            case 'e': vowelCount[1]++; break;
            case 'i': vowelCount[2]++; break;
            case 'o': vowelCount[3]++; break;
            case 'u': vowelCount[4]++; break;
        }
    }
}

int main() {
    char str[100];
    int vowelCount[5];
    char vowels[] = {'a', 'e', 'i', 'o', 'u'};

    printf("Nhap chuoi: ");
    fgets(str, sizeof(str), stdin);
    str[strcspn(str, "\n")] = 0;

    countVowels(str, vowelCount);

    printf("Tan suat cac nguyen am trong '%s':\n", str);
    for (int i = 0; i < 5; i++) {
        printf("%c: %d lan\n", vowels[i], vowelCount[i]);
    }

    return 0;
}

Tổng kết

Con trỏ là chìa khóa để làm chủ C và viết code hiệu quả, chuyên nghiệp.

Lưu ý quan trọng về con trỏ
  • Memory leak: Luôn giải phóng bộ nhớ đã cấp phát
  • Dangling pointer: Không sử dụng con trỏ sau khi giải phóng
  • Segmentation fault: Kiểm tra con trỏ NULL trước khi sử dụng
  • Buffer overflow: Cẩn thận khi thao tác với mảng qua con trỏ
Best Practices
  • Khởi tạo con trỏ với NULL
  • Kiểm tra kết quả malloc/calloc trước khi sử dụng
  • Sử dụng free() cho mỗi malloc/calloc
  • Tránh truy cập bộ nhớ đã được giải phóng
  • Sử dụng const với con trỏ khi không thay đổi dữ liệu

Với những kiến thức này, bạn đã sẵn sàng để tạo ra các chương trình C phức tạp, hiệu quả và tiếp tục khám phá các khái niệm nâng cao như cấu trúc dữ liệu và hệ thống!

Last updated on