Skip to content

Bài 15: Phép toán con trỏ (Pointer Arithmetic)

📖 Giới thiệu

Phép toán con trỏ (Pointer Arithmetic) là khả năng thực hiện các phép tính toán học trên con trỏ để di chuyển qua các vùng nhớ. Đây là một tính năng mạnh mẽ và độc đáo của C++.

Con trỏ không chỉ lưu địa chỉ mà còn có thể "nhảy" từ địa chỉ này sang địa chỉ khác. Điều này rất hữu ích khi làm việc với:

  • Mảng: Duyệt qua các phần tử
  • Chuỗi: Xử lý từng ký tự
  • Cấu trúc dữ liệu: Linked list, tree
  • Memory management: Quản lý vùng nhớ động

Tưởng tượng con trỏ như một "con trỏ chuột" trên màn hình - bạn có thể di chuyển nó lên, xuống, trái, phải để truy cập các vị trí khác nhau.

🔧 Cú pháp

Các phép toán cơ bản

cpp
int arr[] = {10, 20, 30, 40, 50};
int* ptr = arr;  // ptr trỏ đến arr[0]

// Phép cộng: di chuyển về phía sau
ptr + 1;     // Trỏ đến arr[1]
ptr + 2;     // Trỏ đến arr[2]
ptr++;       // Di chuyển đến phần tử tiếp theo
++ptr;       // Tương tự

// Phép trừ: di chuyển về phía trước
ptr - 1;     // Trỏ về phần tử trước đó
ptr--;       // Di chuyển về phần tử trước
--ptr;       // Tương tự

// So sánh con trỏ
ptr1 == ptr2;  // Cùng địa chỉ?
ptr1 < ptr2;   // ptr1 ở địa chỉ thấp hơn?
ptr1 > ptr2;   // ptr1 ở địa chỉ cao hơn?

// Khoảng cách giữa con trỏ
ptr2 - ptr1;   // Số phần tử giữa hai con trỏ

Ví dụ cơ bản

cpp
#include <iostream>
using namespace std;

int main() {
    int numbers[] = {100, 200, 300, 400, 500};
    int* ptr = numbers;  // ptr = &numbers[0]
    
    cout << "Địa chỉ và giá trị:" << endl;
    for (int i = 0; i < 5; i++) {
        cout << "ptr + " << i << ": ";
        cout << "địa chỉ = " << (ptr + i);
        cout << ", giá trị = " << *(ptr + i) << endl;
    }
    
    return 0;
}

🔬 Phân tích & Giải thích chi tiết

1. Phép cộng và trừ con trỏ

cpp
#include <iostream>
using namespace std;

int main() {
    double data[] = {1.1, 2.2, 3.3, 4.4, 5.5};
    double* ptr = data;
    
    cout << "=== PHÉP CỘNG CON TRỎ ===" << endl;
    cout << "ptr ban đầu: " << ptr << " -> " << *ptr << endl;
    
    // Di chuyển từng bước
    ptr++;  // ptr = ptr + 1
    cout << "Sau ptr++: " << ptr << " -> " << *ptr << endl;
    
    ptr += 2;  // ptr = ptr + 2
    cout << "Sau ptr += 2: " << ptr << " -> " << *ptr << endl;
    
    cout << "\n=== PHÉP TRỪ CON TRỎ ===" << endl;
    ptr--;  // ptr = ptr - 1
    cout << "Sau ptr--: " << ptr << " -> " << *ptr << endl;
    
    ptr -= 2;  // ptr = ptr - 2
    cout << "Sau ptr -= 2: " << ptr << " -> " << *ptr << endl;
    
    // Hiển thị kích thước
    cout << "\nKích thước double: " << sizeof(double) << " bytes" << endl;
    cout << "Khi ptr++, địa chỉ tăng " << sizeof(double) << " bytes" << endl;
    
    return 0;
}

2. Duyệt mảng bằng con trỏ

cpp
#include <iostream>
#include <string>
using namespace std;

// Duyệt mảng số nguyên
void printArray(int* arr, int size) {
    cout << "Mảng: ";
    for (int* ptr = arr; ptr < arr + size; ptr++) {
        cout << *ptr << " ";
    }
    cout << endl;
}

// Tìm phần tử lớn nhất
int* findMax(int* arr, int size) {
    if (size <= 0) return nullptr;
    
    int* maxPtr = arr;  // Con trỏ đến phần tử lớn nhất
    
    for (int* ptr = arr + 1; ptr < arr + size; ptr++) {
        if (*ptr > *maxPtr) {
            maxPtr = ptr;
        }
    }
    
    return maxPtr;
}

// Đảo ngược mảng
void reverseArray(int* arr, int size) {
    int* start = arr;
    int* end = arr + size - 1;
    
    while (start < end) {
        // Hoán đổi
        int temp = *start;
        *start = *end;
        *end = temp;
        
        start++;
        end--;
    }
}

int main() {
    int numbers[] = {64, 25, 12, 22, 11, 90, 5};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    cout << "=== DEMO POINTER ARITHMETIC ===" << endl;
    
    // In mảng ban đầu
    cout << "Mảng ban đầu: ";
    printArray(numbers, size);
    
    // Tìm max
    int* maxPtr = findMax(numbers, size);
    if (maxPtr != nullptr) {
        cout << "Phần tử lớn nhất: " << *maxPtr;
        cout << " tại vị trí " << (maxPtr - numbers) << endl;
    }
    
    // Đảo ngược
    reverseArray(numbers, size);
    cout << "Sau khi đảo ngược: ";
    printArray(numbers, size);
    
    return 0;
}

3. Xử lý chuỗi với con trỏ

cpp
#include <iostream>
#include <cctype>
using namespace std;

// Đếm độ dài chuỗi
int myStrlen(const char* str) {
    const char* ptr = str;
    while (*ptr != '\0') {
        ptr++;
    }
    return ptr - str;
}

// Copy chuỗi
void myStrcpy(char* dest, const char* src) {
    while (*src != '\0') {
        *dest = *src;
        dest++;
        src++;
    }
    *dest = '\0';  // Kết thúc chuỗi
}

// So sánh chuỗi
int myStrcmp(const char* str1, const char* str2) {
    while (*str1 && *str2) {
        if (*str1 != *str2) {
            return *str1 - *str2;
        }
        str1++;
        str2++;
    }
    return *str1 - *str2;
}

// Chuyển thành chữ hoa
void toUpperCase(char* str) {
    while (*str) {
        *str = toupper(*str);
        str++;
    }
}

// Đảo ngược chuỗi
void reverseString(char* str) {
    char* start = str;
    char* end = str;
    
    // Tìm cuối chuỗi
    while (*end) {
        end++;
    }
    end--;  // Quay về ký tự cuối
    
    // Đảo ngược
    while (start < end) {
        char temp = *start;
        *start = *end;
        *end = temp;
        start++;
        end--;
    }
}

int main() {
    char text1[] = "Hello World";
    char text2[50];
    char text3[] = "hello world";
    
    cout << "=== XỬ LÝ CHUỖI VỚI CON TRỎ ===" << endl;
    
    // Độ dài chuỗi
    cout << "Độ dài của '" << text1 << "': " << myStrlen(text1) << endl;
    
    // Copy chuỗi
    myStrcpy(text2, text1);
    cout << "Copy: '" << text2 << "'" << endl;
    
    // So sánh chuỗi
    int cmp = myStrcmp(text1, text3);
    cout << "So sánh '" << text1 << "' và '" << text3 << "': " << cmp << endl;
    
    // Chuyển hoa
    toUpperCase(text2);
    cout << "Chữ hoa: '" << text2 << "'" << endl;
    
    // Đảo ngược
    reverseString(text2);
    cout << "Đảo ngược: '" << text2 << "'" << endl;
    
    return 0;
}

💻 Ví dụ minh họa

Chương trình quản lý danh sách điểm số

cpp
#include <iostream>
#include <iomanip>
#include <algorithm>
using namespace std;

class ScoreManager {
private:
    double* scores;
    int size;
    int capacity;

public:
    ScoreManager(int cap = 10) : capacity(cap), size(0) {
        scores = new double[capacity];
    }
    
    ~ScoreManager() {
        delete[] scores;
    }
    
    // Thêm điểm
    void addScore(double score) {
        if (size >= capacity) {
            cout << "❌ Danh sách đã đầy!" << endl;
            return;
        }
        *(scores + size) = score;
        size++;
    }
    
    // Hiển thị tất cả điểm
    void displayScores() const {
        if (size == 0) {
            cout << "📝 Danh sách trống!" << endl;
            return;
        }
        
        cout << "📊 DANH SÁCH ĐIỂM:" << endl;
        for (int i = 0; i < size; i++) {
            cout << "Điểm " << setw(2) << (i + 1) << ": ";
            cout << fixed << setprecision(1) << *(scores + i) << endl;
        }
    }
    
    // Tính điểm trung bình
    double calculateAverage() const {
        if (size == 0) return 0;
        
        double sum = 0;
        for (double* ptr = scores; ptr < scores + size; ptr++) {
            sum += *ptr;
        }
        return sum / size;
    }
    
    // Tìm điểm cao nhất
    double* findMaxScore() const {
        if (size == 0) return nullptr;
        
        double* maxPtr = scores;
        for (double* ptr = scores + 1; ptr < scores + size; ptr++) {
            if (*ptr > *maxPtr) {
                maxPtr = ptr;
            }
        }
        return maxPtr;
    }
    
    // Tìm điểm thấp nhất
    double* findMinScore() const {
        if (size == 0) return nullptr;
        
        double* minPtr = scores;
        for (double* ptr = scores + 1; ptr < scores + size; ptr++) {
            if (*ptr < *minPtr) {
                minPtr = ptr;
            }
        }
        return minPtr;
    }
    
    // Sắp xếp điểm (bubble sort với con trỏ)
    void sortScores() {
        for (double* i = scores; i < scores + size - 1; i++) {
            for (double* j = scores; j < scores + size - 1 - (i - scores); j++) {
                if (*j > *(j + 1)) {
                    // Hoán đổi
                    double temp = *j;
                    *j = *(j + 1);
                    *(j + 1) = temp;
                }
            }
        }
    }
    
    // Đếm số điểm trong khoảng
    int countScoresInRange(double min, double max) const {
        int count = 0;
        for (double* ptr = scores; ptr < scores + size; ptr++) {
            if (*ptr >= min && *ptr <= max) {
                count++;
            }
        }
        return count;
    }
    
    // Lọc điểm theo điều kiện
    void filterScores(double threshold, bool above = true) const {
        cout << "📋 Điểm " << (above ? "trên" : "dưới") << " " << threshold << ":" << endl;
        bool found = false;
        
        for (int i = 0; i < size; i++) {
            double score = *(scores + i);
            if ((above && score >= threshold) || (!above && score < threshold)) {
                cout << "  Điểm " << (i + 1) << ": " << fixed << setprecision(1) << score << endl;
                found = true;
            }
        }
        
        if (!found) {
            cout << "  Không có điểm nào!" << endl;
        }
    }
    
    int getSize() const { return size; }
};

void showMenu() {
    cout << "\n🎯 QUẢN LÝ ĐIỂM SỐ" << endl;
    cout << "1. Thêm điểm" << endl;
    cout << "2. Hiển thị tất cả điểm" << endl;
    cout << "3. Tính điểm trung bình" << endl;
    cout << "4. Tìm điểm cao/thấp nhất" << endl;
    cout << "5. Sắp xếp điểm" << endl;
    cout << "6. Lọc điểm theo điều kiện" << endl;
    cout << "7. Thống kê điểm" << endl;
    cout << "0. Thoát" << endl;
    cout << "Chọn chức năng (0-7): ";
}

int main() {
    ScoreManager manager(20);
    int choice;
    
    do {
        showMenu();
        cin >> choice;
        
        switch (choice) {
            case 1: {
                double score;
                cout << "Nhập điểm (0-10): ";
                cin >> score;
                
                if (score >= 0 && score <= 10) {
                    manager.addScore(score);
                    cout << "✅ Đã thêm điểm " << score << endl;
                } else {
                    cout << "❌ Điểm phải từ 0 đến 10!" << endl;
                }
                break;
            }
            
            case 2:
                manager.displayScores();
                break;
                
            case 3: {
                double avg = manager.calculateAverage();
                cout << "📊 Điểm trung bình: " << fixed << setprecision(2) << avg << endl;
                
                char grade;
                if (avg >= 9.0) grade = 'A';
                else if (avg >= 8.0) grade = 'B';
                else if (avg >= 7.0) grade = 'C';
                else if (avg >= 5.0) grade = 'D';
                else grade = 'F';
                
                cout << "📝 Xếp loại: " << grade << endl;
                break;
            }
            
            case 4: {
                double* maxPtr = manager.findMaxScore();
                double* minPtr = manager.findMinScore();
                
                if (maxPtr && minPtr) {
                    cout << "🏆 Điểm cao nhất: " << *maxPtr << endl;
                    cout << "📉 Điểm thấp nhất: " << *minPtr << endl;
                    cout << "📏 Chênh lệch: " << (*maxPtr - *minPtr) << endl;
                } else {
                    cout << "❌ Danh sách trống!" << endl;
                }
                break;
            }
            
            case 5:
                manager.sortScores();
                cout << "✅ Đã sắp xếp điểm tăng dần!" << endl;
                manager.displayScores();
                break;
                
            case 6: {
                double threshold;
                int filterType;
                cout << "Nhập ngưỡng điểm: ";
                cin >> threshold;
                cout << "1. Điểm >= ngưỡng\n2. Điểm < ngưỡng\nChọn: ";
                cin >> filterType;
                
                manager.filterScores(threshold, filterType == 1);
                break;
            }
            
            case 7: {
                cout << "\n📈 THỐNG KÊ ĐIỂM SỐ" << endl;
                cout << "Tổng số điểm: " << manager.getSize() << endl;
                
                int excellent = manager.countScoresInRange(9.0, 10.0);
                int good = manager.countScoresInRange(8.0, 8.9);
                int average = manager.countScoresInRange(7.0, 7.9);
                int belowAvg = manager.countScoresInRange(5.0, 6.9);
                int poor = manager.countScoresInRange(0.0, 4.9);
                
                cout << "Xuất sắc (9.0-10.0): " << excellent << " điểm" << endl;
                cout << "Giỏi (8.0-8.9): " << good << " điểm" << endl;
                cout << "Khá (7.0-7.9): " << average << " điểm" << endl;
                cout << "Trung bình (5.0-6.9): " << belowAvg << " điểm" << endl;
                cout << "Yếu (0.0-4.9): " << poor << " điểm" << endl;
                break;
            }
            
            case 0:
                cout << "👋 Cảm ơn bạn đã sử dụng chương trình!" << endl;
                break;
                
            default:
                cout << "❌ Lựa chọn không hợp lệ!" << endl;
        }
        
    } while (choice != 0);
    
    return 0;
}

🏋️ Thực hành

Bài tập 1: Tìm kiếm trong mảng

cpp
#include <iostream>
using namespace std;

// Tìm kiếm tuyến tính
int* linearSearch(int* arr, int size, int target) {
    for (int* ptr = arr; ptr < arr + size; ptr++) {
        if (*ptr == target) {
            return ptr;  // Trả về con trỏ đến vị trí tìm thấy
        }
    }
    return nullptr;  // Không tìm thấy
}

// Tìm kiếm nhị phân (mảng đã sắp xếp)
int* binarySearch(int* arr, int size, int target) {
    int* left = arr;
    int* right = arr + size - 1;
    
    while (left <= right) {
        int* mid = left + (right - left) / 2;
        
        if (*mid == target) {
            return mid;
        } else if (*mid < target) {
            left = mid + 1;
        } else {
            right = mid - 1;
        }
    }
    return nullptr;
}

int main() {
    int numbers[] = {10, 25, 30, 45, 60, 75, 90};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    int target;
    
    cout << "Mảng: ";
    for (int i = 0; i < size; i++) {
        cout << numbers[i] << " ";
    }
    cout << endl;
    
    cout << "Nhập số cần tìm: ";
    cin >> target;
    
    // Tìm kiếm tuyến tính
    int* result = linearSearch(numbers, size, target);
    if (result != nullptr) {
        int index = result - numbers;
        cout << "Tìm thấy " << target << " tại vị trí " << index << endl;
    } else {
        cout << "Không tìm thấy " << target << endl;
    }
    
    return 0;
}

Bài tập 2: Sắp xếp với con trỏ

cpp
#include <iostream>
using namespace std;

// Selection sort với con trỏ
void selectionSort(int* arr, int size) {
    for (int* i = arr; i < arr + size - 1; i++) {
        int* minPtr = i;
        
        // Tìm phần tử nhỏ nhất
        for (int* j = i + 1; j < arr + size; j++) {
            if (*j < *minPtr) {
                minPtr = j;
            }
        }
        
        // Hoán đổi
        if (minPtr != i) {
            int temp = *i;
            *i = *minPtr;
            *minPtr = temp;
        }
    }
}

void printArray(int* arr, int size) {
    for (int* ptr = arr; ptr < arr + size; ptr++) {
        cout << *ptr << " ";
    }
    cout << endl;
}

int main() {
    int data[] = {64, 25, 12, 22, 11, 90, 88, 76, 50, 42};
    int size = sizeof(data) / sizeof(data[0]);
    
    cout << "Mảng ban đầu: ";
    printArray(data, size);
    
    selectionSort(data, size);
    
    cout << "Mảng sau sắp xếp: ";
    printArray(data, size);
    
    return 0;
}

📋 Tóm tắt

Kiến thức đã học:

  1. Phép toán con trỏ cơ bản:

    • ptr++, ptr--: Di chuyển đến phần tử tiếp theo/trước
    • ptr + n, ptr - n: Di chuyển n phần tử
    • ptr2 - ptr1: Khoảng cách giữa hai con trỏ
  2. So sánh con trỏ:

    • ==, !=: So sánh địa chỉ
    • <, >, <=, >=: So sánh vị trí trong bộ nhớ
  3. Ứng dụng thực tế:

    • Duyệt mảng hiệu quả
    • Xử lý chuỗi ký tự
    • Cài đặt thuật toán tìm kiếm, sắp xếp
    • Quản lý bộ nhớ động

Lưu ý quan trọng:

  • Bounds checking: Luôn kiểm tra giới hạn mảng
  • Null pointer: Kiểm tra con trỏ trước khi sử dụng
  • Type safety: Chỉ cộng/trừ con trỏ cùng kiểu
  • Memory leaks: Giải phóng bộ nhớ đúng cách

Chuẩn bị cho bài tiếp theo:

  • Dynamic memory allocation (new/delete)
  • Memory management best practices
  • Smart pointers introduction
  • Memory leaks prevention

"Pointer arithmetic giống như di chuyển trong mê cung - biết đường đi đúng sẽ đến đích nhanh, nhưng đi sai có thể... lạc mất!"


Chuyển sang: Quản lý bộ nhớ động để học cách cấp phát và giải phóng memory!

Khóa học C++ miễn phí