指针是 C++ 中最重要也是最具挑战性的概念之一。指针是一个变量,它存储另一个变量的内存地址。理解指针对于掌握 C++ 至关重要。

指针的基本概念

什么是指针

#include <iostream>
 
int main() {
    // 普通变量
    int x = 42;
    
    // 指针变量:存储其他变量的地址
    int* ptr = &x;  // ptr 指向 x 的地址
    
    std::cout << "变量 x 的值: " << x << std::endl;
    std::cout << "变量 x 的地址: " << &x << std::endl;
    std::cout << "指针 ptr 的值(x的地址): " << ptr << std::endl;
    std::cout << "指针 ptr 指向的值: " << *ptr << std::endl;
    std::cout << "指针 ptr 自身的地址: " << &ptr << std::endl;
    
    // 内存布局示意
    std::cout << "\n内存布局:" << std::endl;
    std::cout << "地址\t\t变量\t值" << std::endl;
    std::cout << &x << "\tx\t" << x << std::endl;
    std::cout << &ptr << "\tptr\t" << ptr << std::endl;
    
    return 0;
}

指针的声明和初始化

#include <iostream>
 
int main() {
    // 1. 指针声明的不同方式
    int* ptr1;      // 推荐:* 靠近类型
    int *ptr2;      // 也可以:* 靠近变量名
    int * ptr3;     // 也可以:* 两边都有空格
    
    // 2. 多个指针声明
    int *p1, *p2;   // p1 和 p2 都是指针
    int* p3, p4;    // 注意:p3 是指针,p4 是普通 int 变量!
    
    // 3. 指针初始化
    int value = 100;
    int* ptr = &value;          // 用变量地址初始化
    int* ptr_null = nullptr;    // C++11:空指针
    int* ptr_zero = 0;          // 传统:用 0 初始化(不推荐)
    int* ptr_NULL = NULL;       // C 风格:用 NULL 初始化(不推荐)
    
    // 4. 未初始化的指针(危险!)
    int* dangerous_ptr;  // 包含垃圾值,使用前必须初始化
    // std::cout << *dangerous_ptr;  // 未定义行为!
    
    std::cout << "value = " << value << std::endl;
    std::cout << "ptr 指向的值 = " << *ptr << std::endl;
    std::cout << "ptr_null = " << ptr_null << std::endl;
    
    return 0;
}

指针的基本操作

取地址运算符和解引用运算符

#include <iostream>
 
int main() {
    int x = 10;
    int y = 20;
    
    // 取地址运算符 &
    int* ptr = &x;
    
    std::cout << "初始状态:" << std::endl;
    std::cout << "x = " << x << ", 地址 = " << &x << std::endl;
    std::cout << "y = " << y << ", 地址 = " << &y << std::endl;
    std::cout << "ptr = " << ptr << std::endl;
    
    // 解引用运算符 *
    std::cout << "\n通过指针访问:" << std::endl;
    std::cout << "*ptr = " << *ptr << std::endl;
    
    // 通过指针修改值
    *ptr = 50;
    std::cout << "\n修改 *ptr = 50 后:" << std::endl;
    std::cout << "x = " << x << std::endl;  // x 也变成了 50
    std::cout << "*ptr = " << *ptr << std::endl;
    
    // 改变指针指向
    ptr = &y;
    std::cout << "\n改变指针指向 y 后:" << std::endl;
    std::cout << "ptr = " << ptr << std::endl;
    std::cout << "*ptr = " << *ptr << std::endl;
    
    // 通过新指向修改值
    *ptr = 100;
    std::cout << "\n修改 *ptr = 100 后:" << std::endl;
    std::cout << "y = " << y << std::endl;  // y 变成了 100
    std::cout << "x = " << x << std::endl;  // x 保持 50 不变
    
    return 0;
}

指针运算

#include <iostream>
 
int main() {
    int arr[5] = {10, 20, 30, 40, 50};
    int* ptr = arr;  // 指向数组第一个元素
    
    std::cout << "数组元素和地址:" << std::endl;
    for (int i = 0; i < 5; ++i) {
        std::cout << "arr[" << i << "] = " << arr[i] 
                  << ", 地址 = " << &arr[i] << std::endl;
    }
    
    std::cout << "\n指针运算:" << std::endl;
    
    // 指针递增
    std::cout << "ptr 指向 arr[0]: " << *ptr << std::endl;
    ptr++;  // 移动到下一个元素
    std::cout << "ptr++ 后指向 arr[1]: " << *ptr << std::endl;
    
    // 指针加法
    ptr = arr;  // 重置到开始
    std::cout << "*(ptr + 2) = " << *(ptr + 2) << std::endl;  // arr[2]
    std::cout << "*(ptr + 4) = " << *(ptr + 4) << std::endl;  // arr[4]
    
    // 指针减法
    int* ptr1 = &arr[4];
    int* ptr2 = &arr[1];
    ptrdiff_t diff = ptr1 - ptr2;  // 指针之间的距离
    std::cout << "ptr1 - ptr2 = " << diff << " 个元素" << std::endl;
    
    // 指针比较
    if (ptr1 > ptr2) {
        std::cout << "ptr1 指向更高的内存地址" << std::endl;
    }
    
    // 数组下标等价于指针运算
    ptr = arr;
    std::cout << "\n数组下标 vs 指针运算:" << std::endl;
    for (int i = 0; i < 5; ++i) {
        std::cout << "arr[" << i << "] = " << arr[i] 
                  << ", *(ptr + " << i << ") = " << *(ptr + i) << std::endl;
    }
    
    return 0;
}

指针与数组

数组名作为指针

#include <iostream>
 
void printArray(int* arr, int size) {
    std::cout << "在函数中:" << std::endl;
    std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl;  // 指针大小
    
    for (int i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
}
 
int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    std::cout << "在 main 中:" << std::endl;
    std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl;  // 整个数组大小
    
    // 数组名可以隐式转换为指向第一个元素的指针
    int* ptr = arr;  // 等价于 int* ptr = &arr[0];
    
    std::cout << "arr = " << arr << std::endl;
    std::cout << "&arr[0] = " << &arr[0] << std::endl;
    std::cout << "ptr = " << ptr << std::endl;
    
    // 数组名和指针的区别
    std::cout << "\n数组名 vs 指针:" << std::endl;
    std::cout << "sizeof(arr) = " << sizeof(arr) << std::endl;    // 20 (5 * 4)
    std::cout << "sizeof(ptr) = " << sizeof(ptr) << std::endl;    // 8 (64位系统)
    
    // 数组名不能被赋值(它是常量)
    // arr = ptr;  // 错误!
    ptr = arr;     // 正确
    
    // 传递数组给函数
    printArray(arr, 5);  // 数组退化为指针
    printArray(ptr, 5);  // 直接传递指针
    
    return 0;
}

指针数组 vs 数组指针

#include <iostream>
 
int main() {
    // 1. 指针数组:数组的每个元素都是指针
    int a = 10, b = 20, c = 30;
    int* ptrArray[3] = {&a, &b, &c};  // 3 个指针的数组
    
    std::cout << "指针数组:" << std::endl;
    for (int i = 0; i < 3; ++i) {
        std::cout << "ptrArray[" << i << "] = " << ptrArray[i] 
                  << ", *ptrArray[" << i << "] = " << *ptrArray[i] << std::endl;
    }
    
    // 2. 数组指针:指向数组的指针
    int arr[3] = {100, 200, 300};
    int (*arrayPtr)[3] = &arr;  // 指向包含3个int的数组的指针
    
    std::cout << "\n数组指针:" << std::endl;
    std::cout << "arrayPtr = " << arrayPtr << std::endl;
    std::cout << "*arrayPtr = " << *arrayPtr << std::endl;  // 指向数组第一个元素
    
    // 通过数组指针访问元素
    for (int i = 0; i < 3; ++i) {
        std::cout << "(*arrayPtr)[" << i << "] = " << (*arrayPtr)[i] << std::endl;
    }
    
    // 3. 字符串指针数组
    const char* strings[] = {"Hello", "World", "C++"};
    std::cout << "\n字符串指针数组:" << std::endl;
    for (int i = 0; i < 3; ++i) {
        std::cout << "strings[" << i << "] = " << strings[i] << std::endl;
    }
    
    // 4. 二维数组和指针
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    int (*matrixPtr)[3] = matrix;  // 指向包含3个int的数组的指针
    
    std::cout << "\n二维数组指针:" << std::endl;
    for (int i = 0; i < 2; ++i) {
        for (int j = 0; j < 3; ++j) {
            std::cout << "matrixPtr[" << i << "][" << j << "] = " 
                      << matrixPtr[i][j] << " ";
        }
        std::cout << std::endl;
    }
    
    return 0;
}

动态内存分配

new 和 delete 操作符

#include <iostream>
 
int main() {
    // 1. 动态分配单个变量
    int* ptr = new int(42);  // 分配并初始化
    std::cout << "动态分配的值: " << *ptr << std::endl;
    delete ptr;  // 释放内存
    ptr = nullptr;  // 避免悬空指针
    
    // 2. 动态分配数组
    int size = 5;
    int* arr = new int[size];  // 分配数组
    
    // 初始化数组
    for (int i = 0; i < size; ++i) {
        arr[i] = i * 10;
    }
    
    std::cout << "动态数组: ";
    for (int i = 0; i < size; ++i) {
        std::cout << arr[i] << " ";
    }
    std::cout << std::endl;
    
    delete[] arr;  // 释放数组内存
    arr = nullptr;
    
    // 3. 动态分配二维数组
    int rows = 3, cols = 4;
    
    // 方法1:指针数组
    int** matrix1 = new int*[rows];
    for (int i = 0; i < rows; ++i) {
        matrix1[i] = new int[cols];
    }
    
    // 初始化
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            matrix1[i][j] = i * cols + j;
        }
    }
    
    std::cout << "动态二维数组:" << std::endl;
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            std::cout << matrix1[i][j] << "\t";
        }
        std::cout << std::endl;
    }
    
    // 释放二维数组
    for (int i = 0; i < rows; ++i) {
        delete[] matrix1[i];
    }
    delete[] matrix1;
    
    // 方法2:一维数组模拟二维
    int* matrix2 = new int[rows * cols];
    for (int i = 0; i < rows * cols; ++i) {
        matrix2[i] = i;
    }
    
    std::cout << "一维数组模拟二维:" << std::endl;
    for (int i = 0; i < rows; ++i) {
        for (int j = 0; j < cols; ++j) {
            std::cout << matrix2[i * cols + j] << "\t";
        }
        std::cout << std::endl;
    }
    
    delete[] matrix2;
    
    return 0;
}

内存泄漏和悬空指针

#include <iostream>
 
void demonstrateMemoryLeaks() {
    // 1. 内存泄漏示例
    int* ptr1 = new int(100);
    ptr1 = new int(200);  // 内存泄漏!原来的内存没有释放
    delete ptr1;  // 只释放了第