Kỹ Thuật Thao Tác Mảng Và Chuỗi Nâng Cao Với Con Trỏ Trong C

Quản Lý Bộ Nhớ Mảng Và Tìm Kiếm Giá Trị Cực Trị

Trong ngôn ngữ C, tên của mảng khi được truyền vào hàm sẽ tự động suy biến (decay) thành con trỏ trỏ đến phần tử đầu tiên của mảng đó (tương đương với địa chỉ của arr[0]). Dựa trên đặc tính này, chúng ta có thể xây dựng các hàm để nhập, xuất và xử lý dữ liệu mảng một cách hiệu quả thông qua việc truyền địa chỉ.

Đoạn mã dưới đây minh họa cách sử dụng con trỏ để duyệt mảng nhằm tìm ra đồng thời giá trị lớn nhất và nhỏ nhất, cũng như cách trả về con trỏ trỏ trực tiếp đến phần tử mang giá trị lớn nhất.

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

#define ELEMENT_COUNT 5

void read_data(int *buffer, size_t size);
void print_data(const int *buffer, size_t size);
void evaluate_bounds(const int *buffer, size_t size, int *out_min, int *out_max);
const int* locate_peak(const int *buffer, size_t size);

int main() {
    int dataset[ELEMENT_COUNT];
    int lowest, highest;
    const int *peak_ptr;

    printf("Nhap %d phan tu:\n", ELEMENT_COUNT);
    read_data(dataset, ELEMENT_COUNT);

    printf("Mang du lieu: ");
    print_data(dataset, ELEMENT_COUNT);

    evaluate_bounds(dataset, ELEMENT_COUNT, &lowest, &highest);
    printf("Gia tri nho nhat: %d | Gia tri lon nhat: %d\n", lowest, highest);
    
    peak_ptr = locate_peak(dataset, ELEMENT_COUNT);
    if (peak_ptr != NULL) {
        printf("Dia chi chua max: %p, Gia tri: %d\n", (void*)peak_ptr, *peak_ptr);
    }

    return 0;
}

void read_data(int *buffer, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        scanf("%d", buffer + i);
    }
}

void print_data(const int *buffer, size_t size) {
    for (size_t i = 0; i < size; ++i) {
        printf("%d ", buffer[i]);
    }
    printf("\n");
}

void evaluate_bounds(const int *buffer, size_t size, int *out_min, int *out_max) {
    if (size == 0) return;
    *out_min = *out_max = buffer[0];
    for (size_t i = 1; i < size; ++i) {
        if (buffer[i] < *out_min) *out_min = buffer[i];
        else if (buffer[i] > *out_max) *out_max = buffer[i];
    }
}

const int* locate_peak(const int *buffer, size_t size) {
    if (size == 0) return NULL;
    const int *max_element = &buffer[0];
    for (size_t i = 1; i < size; ++i) {
        if (buffer[i] > *max_element) {
            max_element = &buffer[i];
        }
    }
    return max_element;
}

Phân Biệt Kích Thước Bộ Nhớ Và Độ Dài Chuỗi

Một khái niệm dễ gây nhầm lẫn khi làm việc với chuỗi ký tự là sự khác biệt giữa toán tử sizeof và hàm strlen():

  • sizeof: Trả về tổng số byte mà biến được cấp phát trong bộ nhớ. Đối với mảng ký tự, nó bao gồm cả bộ nhớ đệm dư thừa và ký tự null (\0).
  • strlen(): Đếm số lượng ký tự thực tế của chuỗi tính đến trước ký tự \0, không bao gồm bản thân ký tự kết thúc này.

Khi thực hiện hoán đổi nội dung giữa hai chuỗi, phương pháp thực hiện phụ thuộc vào cách khai báo chuỗi. Nếu chuỗi được khai báo dưới dạng mảng ký tự (ví dụ: char s[N]), tên mảng là một hằng con trỏ và không thể gán lại địa chỉ. Việc hoán đổi bắt buộc phải sao chép nội dung thông qua một mảng trung gian (sử dụng strcpy). Ngược lại, nếu chuỗi được khai báo dưới dạng con trỏ (ví dụ: char *s), chúng ta chỉ cần tráo đổi địa chỉ mà các con trỏ đang nắm giữ, không hề có sự di chuyển dữ liệu nào trên bộ nhớ heap hay stack.

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

#define BUFFER_SIZE 80

int main() {
    // Hoán đổi nội dung mảng
    char arr1[BUFFER_SIZE] = "System initialization";
    char arr2[BUFFER_SIZE] = "System termination";
    char temp_arr[BUFFER_SIZE];

    printf("Kich thuoc cap phat cua arr1: %zu bytes\n", sizeof(arr1));
    printf("Do dai thuc te cua arr1: %zu ky tu\n", strlen(arr1));

    strcpy(temp_arr, arr1);
    strcpy(arr1, arr2);
    strcpy(arr2, temp_arr);
    
    printf("Sau khi hoan doi mang: arr1='%s', arr2='%s'\n\n", arr1, arr2);

    // Hoán đổi địa chỉ con trỏ
    const char *ptr1 = "Alpha protocol";
    const char *ptr2 = "Omega protocol";
    const char *temp_ptr;

    temp_ptr = ptr1;
    ptr1 = ptr2;
    ptr2 = temp_ptr;

    printf("Sau khi hoan doi con tro: ptr1='%s', ptr2='%s'\n", ptr1, ptr2);

    return 0;
}

Truy Cập Mảng Hai Chiều Qua Con Trỏ

Mảng hai chiều trong C thực chất là một mảng của các mảng một chiều. Chúng ta có thể duyệt qua ma trận này theo nhiều cách: sử dụng chỉ số truyền thống, sử dụng con trỏ phẳng (ép kiểu mảng 2D thành vùng nhớ liên tục 1D), hoặc sử dụng con trỏ mảng (pointer to an array) để trỏ đến từng hàng.

#include <stdio.h>

int main() {
    int matrix[2][4] = {
        {11, 22, 33, 44}, 
        {55, 66, 77, 88}
    };
    
    int *flat_pointer = &matrix[0][0]; 
    int (*row_pointer)[4] = matrix;    

    printf("Duyet bang con tro phang (1D):\n");
    for (int i = 0; i < 8; ++i) {
        printf("%d ", *(flat_pointer + i));
        if ((i + 1) % 4 == 0) printf("\n");
    }
                         
    printf("\nDuyet bang con tro hang (2D):\n");
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 4; ++j) {
            printf("%d ", *(*(row_pointer + i) + j));
        }
        printf("\n");
    }
    
    return 0;
}

Thao Tác Biến Đổi Và Cắt Ngắn Chuỗi

Việc xử lý văn bản thường yêu cầu các thao tác thay thế ký tự hoặc cắt bỏ một phần chuỗi. Khi thao tác trực tiếp qua con trỏ, vòng lặp while(*ptr) là một kỹ thuật chuẩn xác để duyệt đến cuối chuỗi (nơi gặp ký tự \0).

Đối với việc cắt chuỗi, thay vì dùng vòng lặp thủ công, hàm strchr() từ thư viện <string.h> cung cấp một giải pháp tối ưu hơn để tìm kiếm vị trí của ký tự mục tiêu và chèn ký tự kết thúc chuỗi tại đó. Lưu ý khi xử lý đầu vào văn bản liên tục, việc sử dụng gets() bị coi là lỗi thời và không an toàn. Thay vào đó, fgets() nên được ưu tiên, kết hợp với việc làm sạch bộ đệm để tránh lỗi đọc sót ký tự xuống dòng (\n).

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

#define MAX_TEXT 100

void substitute_chars(char *text, char target, char replacement) {
    while (*text) {
        if (*text == target) {
            *text = replacement;
        }
        text++;
    }
}

char* truncate_string(char *text, char delimiter) {
    char *pos = strchr(text, delimiter);
    if (pos != NULL) {
        *pos = '\0';
    }
    return text;
}

int main() {
    char document[MAX_TEXT] = "Programming logic is flawless";
    
    printf("Van ban goc: %s\n", document);
    substitute_chars(document, 'l', '#');
    printf("Sau khi thay the 'l': %s\n", document);

    char sample[MAX_TEXT] = "Data:12345:End";
    printf("\nChuoi truoc khi cat: %s\n", sample);
    truncate_string(sample, ':');
    printf("Chuoi sau khi cat tai dau ':': %s\n", sample);

    return 0;
}

Xác Thực Định Dạng Định Danh

Kiểm tra tính hợp lệ của một chuỗi định danh (ví dụ: mã số cá nhân) yêu cầu việc xác nhận cả độ dài chuỗi và kiểu ký tự ở từng vị trí. Sử dụng thư viện <ctype.h> cung cấp các hàm tiện ích như isdigit() giúp mã nguồn trở nên rõ ràng và dễ bảo trì hơn so với việc so sánh mã ASCII thủ công.

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

#define ID_COUNT 4

int validate_identity(const char *id_str) {
    if (strlen(id_str) != 18) {
        return 0; 
    }

    for (int i = 0; i < 17; i++) {
        if (!isdigit((unsigned char)id_str[i])) {
            return 0;
        }
    }
    
    char last_char = toupper((unsigned char)id_str[17]);
    if (!isdigit(last_char) && last_char != 'X') {
        return 0;
    }
    
    return 1;
}

int main() {
    const char *identities[ID_COUNT] = {
        "31010120000721656X",
        "3301061996X0203301",
        "53010220051126571",
        "510104199211197977"
    };

    for (int i = 0; i < ID_COUNT; ++i) {
        if (validate_identity(identities[i])) {
            printf("%s\t[Hop le]\n", identities[i]);
        } else {
            printf("%s\t[Khong hop le]\n", identities[i]);
        }
    }
    
    return 0;
}

Thuật Toán Mã Hóa Và Giải Mã Caesar

Mã hóa Caesar là một kỹ thuật thay thế ký tự bằng cách dịch chuyển bảng chữ cái một số bước nhất định. Một lỗi logic phổ biến khi hiện thực hóa thuật toán này là việc cộng/trừ trực tiếp vào mã ASCII mà không kiểm soát giới hạn, dẫn đến việc tạo ra các ký tự ngoại bảng chữ cái. Để khắc phục, cần áp dụng phép chia lấy dư (modulo) để đảm bảo các ký tự được cuộn vòng (wrap-around) chính xác trong phạm vi từ 'a' đến 'z' hoặc 'A' đến 'Z'. Các ký tự không phải chữ cái sẽ được giữ nguyên.

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

#define TEXT_LEN 100

void caesar_encrypt(char *text, int shift) {
    shift = shift % 26;
    for (int i = 0; text[i] != '\0'; i++) {
        if (isalpha((unsigned char)text[i])) {
            char base = islower((unsigned char)text[i]) ? 'a' : 'A';
            text[i] = (char)((text[i] - base + shift + 26) % 26 + base);
        }
    }
}

void caesar_decrypt(char *text, int shift) {
    shift = shift % 26;
    for (int i = 0; text[i] != '\0'; i++) {
        if (isalpha((unsigned char)text[i])) {
            char base = islower((unsigned char)text[i]) ? 'a' : 'A';
            text[i] = (char)((text[i] - base - shift + 26) % 26 + base);
        }
    }
}

int main() {
    char message[TEXT_LEN] = "Protect the source code!";
    int key = 3;

    printf("Thong diep ban dau: %s\n", message);
    
    caesar_encrypt(message, key);
    printf("Da ma hoa (dich %d): %s\n", key, message);

    caesar_decrypt(message, key);
    printf("Da giai ma: %s\n", message);

    return 0;
}

Thẻ: C pointers arrays string manipulation memory management

Đăng vào ngày 27 tháng 8 lúc 16:13