深入指针3 - 完全精讲版

文章目录

  • [**深入指针3 - 完全精讲版**](#深入指针3 - 完全精讲版)
    • **一、数组名的深度理解**
      • [**1. 验证:数组名 = 数组首地址**](#1. 验证:数组名 = 数组首地址)
      • [**2. 验证:sizeof(数组名)计算整个数组大小**](#2. 验证:sizeof(数组名)计算整个数组大小)
      • [**3. 验证:&arr取的是整个数组的地址**](#3. 验证:&arr取的是整个数组的地址)
    • **二、区分数组名地址和数组地址有什么用?**
      • [**指针类型决定步长 - 深入剖析**](#指针类型决定步长 - 深入剖析)
      • **实际应用场景**
    • [**三、使用指针访问数组 - 全方位解析**](#三、使用指针访问数组 - 全方位解析)
      • [**1. 指针访问的多种写法**](#1. 指针访问的多种写法)
      • [**2. 指针运算的细节**](#2. 指针运算的细节)
      • [**3. 指针访问的边界问题**](#3. 指针访问的边界问题)
    • [**四、一维数组传参的本质 - 深入剖析**](#四、一维数组传参的本质 - 深入剖析)
      • [**1. 验证传参本质**](#1. 验证传参本质)
      • [**2. 为什么必须传递数组长度**](#2. 为什么必须传递数组长度)
      • [**3. 多维数组传参**](#3. 多维数组传参)
    • [**五、冒泡排序和选择排序 - 完整实现与优化**](#五、冒泡排序和选择排序 - 完整实现与优化)
      • [**1. 冒泡排序详解**](#1. 冒泡排序详解)
      • [**2. 选择排序详解**](#2. 选择排序详解)
      • [**3. 排序算法对比**](#3. 排序算法对比)
    • [**六、二级指针 - 深入理解**](#六、二级指针 - 深入理解)
      • [**1. 二级指针的基本概念**](#1. 二级指针的基本概念)
      • [**2. 二级指针的典型应用**](#2. 二级指针的典型应用)
      • [**3. 多级指针的灵活运用**](#3. 多级指针的灵活运用)
    • [**七、指针数组 - 完整解析**](#七、指针数组 - 完整解析)
      • [**1. 指针数组的基本概念**](#1. 指针数组的基本概念)
      • [**2. 不同类型指针数组**](#2. 不同类型指针数组)
    • **八、指针数组格式书写举例**
      • [**1. 各种指针数组定义示例**](#1. 各种指针数组定义示例)
      • [**2. 复杂指针数组声明解析**](#2. 复杂指针数组声明解析)
    • [**九、指针数组模拟二维数组 - 深入剖析**](#九、指针数组模拟二维数组 - 深入剖析)
      • [**1. 基本原理和内存布局**](#1. 基本原理和内存布局)
      • [**2. 与真实二维数组的对比**](#2. 与真实二维数组的对比)
      • [**3. 动态创建和操作**](#3. 动态创建和操作)
    • [**十、字符指针变量 - 全方位解析**](#十、字符指针变量 - 全方位解析)
      • [**1. 字符指针的基础用法**](#1. 字符指针的基础用法)
      • [**2. 字符指针的运算**](#2. 字符指针的运算)
      • [**3. 字符指针数组 - 字符串数组**](#3. 字符指针数组 - 字符串数组)
    • [**十一、不可以修改常量字符串的值 - 深入理解**](#十一、不可以修改常量字符串的值 - 深入理解)
      • [**1. 为什么不能修改**](#1. 为什么不能修改)
      • [**2. 内存分区详解**](#2. 内存分区详解)
    • [**十二、《剑指offer》字符串笔试题 - 详细解析**](#十二、《剑指offer》字符串笔试题 - 详细解析)
      • [**1. 原题解析**](#1. 原题解析)
      • [**2. 深入扩展**](#2. 深入扩展)
    • [**十三、数组指针 - 完整精讲**](#十三、数组指针 - 完整精讲)
      • [**1. 数组指针的基本概念**](#1. 数组指针的基本概念)
      • [**2. 数组指针的声明解析**](#2. 数组指针的声明解析)
      • [**3. 数组指针的应用场景**](#3. 数组指针的应用场景)
      • [**4. 数组指针与指针数组对比总结**](#4. 数组指针与指针数组对比总结)
    • **总结:指针核心概念速查表**

深入指针3 - 完全精讲版


一、数组名的深度理解

数组名在C语言中是一个特殊的存在,它既是地址,又不完全是地址,需要深入理解其本质。

1. 验证:数组名 = 数组首地址

c 复制代码
#include <stdio.h>
int main() {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    printf("&arr[0] = %p\n", &arr[0]);  // 第一个元素的地址
    printf("arr     = %p\n", arr);       // 数组名
    printf("&arr    = %p\n", &arr);      // 整个数组的地址
    
    // 额外验证:打印地址的整数值
    printf("&arr[0] = %lu\n", (unsigned long)&arr[0]);
    printf("arr     = %lu\n", (unsigned long)arr);
    return 0;
}

运行结果:

复制代码
&arr[0] = 0060FEF0
arr     = 0060FEF0
&arr[0] = 6355696
arr     = 6355696

为什么是这样?

  • 数组在内存中是连续存储的
  • 数组名就是这块连续内存的起始地址
  • 第一个元素的地址自然就是数组的起始地址

2. 验证:sizeof(数组名)计算整个数组大小

c 复制代码
#include <stdio.h>
int main() {
    // 测试不同数据类型的数组
    int arr_int[10] = {0};
    char arr_char[10] = {0};
    double arr_double[10] = {0};
    
    printf("int数组:\n");
    printf("  sizeof(arr_int[0]) = %d\n", sizeof(arr_int[0]));    // 4
    printf("  sizeof(arr_int)    = %d\n", sizeof(arr_int));       // 40
    printf("  数组长度 = %d\n", sizeof(arr_int) / sizeof(arr_int[0])); // 10
    
    printf("\nchar数组:\n");
    printf("  sizeof(arr_char[0]) = %d\n", sizeof(arr_char[0]));  // 1
    printf("  sizeof(arr_char)    = %d\n", sizeof(arr_char));     // 10
    printf("  数组长度 = %d\n", sizeof(arr_char) / sizeof(arr_char[0])); // 10
    
    printf("\ndouble数组:\n");
    printf("  sizeof(arr_double[0]) = %d\n", sizeof(arr_double[0])); // 8
    printf("  sizeof(arr_double)    = %d\n", sizeof(arr_double));    // 80
    printf("  数组长度 = %d\n", sizeof(arr_double) / sizeof(arr_double[0])); // 10
    
    return 0;
}

重要发现:

  • sizeof(数组名) = 数组元素类型大小 × 元素个数
  • 这就是为什么常用sizeof(arr)/sizeof(arr[0])计算数组长度
  • 注意: 这个公式只在数组定义的作用域内有效

3. 验证:&arr取的是整个数组的地址

c 复制代码
#include <stdio.h>
int main() {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    
    printf("三个地址的值相同:\n");
    printf("&arr[0] = %p\n", &arr[0]);
    printf("arr     = %p\n", arr);
    printf("&arr    = %p\n", &arr);
    
    printf("\n但它们的类型不同,导致+1结果不同:\n");
    printf("&arr[0]+1 = %p (跳过一个int)\n", &arr[0]+1);
    printf("arr+1     = %p (跳过一个int)\n", arr+1);
    printf("&arr+1    = %p (跳过整个数组)\n", &arr+1);
    
    // 计算跳过的字节数
    printf("\n跳过的字节数:\n");
    printf("arr+1 跳过: %d 字节\n", (char*)(arr+1) - (char*)arr);
    printf("&arr+1 跳过: %d 字节\n", (char*)(&arr+1) - (char*)&arr);
    
    return 0;
}

内存布局图解:

复制代码
数组arr[10]的内存布局:
+----+----+----+----+----+----+----+----+----+----+
| 1  | 2  | 3  | 4  | 5  | 6  | 7  | 8  | 9  | 10 |
+----+----+----+----+----+----+----+----+----+----+
^    ^                        ^                    ^
|    |                        |                    |
arr  arr+1                   arr+5                &arr+1
(arr[0]) (arr[1])            (arr[5])              (越过数组)

arr = 0x1000
arr+1 = 0x1004
&arr+1 = 0x1028 (0x1000 + 40)

二、区分数组名地址和数组地址有什么用?

指针类型决定步长 - 深入剖析

c 复制代码
#include <stdio.h>
int main() {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    
    // 1. 验证不同类型的指针步长
    int* p1 = arr;           // 首元素指针,类型 int*
    int* p2 = &arr[0];       // 首元素指针,类型 int*
    int(*p3)[10] = &arr;     // 整个数组指针,类型 int(*)[10]
    
    printf("p1 类型: int*, 大小: %d 字节\n", sizeof(p1));
    printf("p3 类型: int(*)[10], 大小: %d 字节\n", sizeof(p3));
    printf("但p1和p3本身都是指针,都占4字节\n\n");
    
    printf("p1 指向的内容大小: %d 字节\n", sizeof(*p1));     // 4字节
    printf("p3 指向的内容大小: %d 字节\n", sizeof(*p3));     // 40字节
    
    // 2. 实际应用:数组遍历方式对比
    printf("\n使用int*遍历数组:\n");
    for(int i = 0; i < 10; i++) {
        printf("%d ", *(p1 + i));
    }
    
    // 3. 使用数组指针的错误示例
    // for(int i = 0; i < 10; i++) {
    //     printf("%d ", *(*p3 + i));  // *p3得到数组首地址,再加i
    // }
    
    // 4. 二级指针和数组指针的关系
    int** pp = &p1;           // 二级指针,指向int*
    // int(*p3)[10] = &arr;    // 数组指针,指向int[10]
    
    return 0;
}

实际应用场景

c 复制代码
#include <stdio.h>

// 场景1:函数需要处理整个数组的地址
void print_array(int (*p)[10]) {  // 接收数组指针
    for(int i = 0; i < 10; i++) {
        printf("%d ", (*p)[i]);    // (*p)得到数组名,再下标访问
    }
}

// 场景2:函数处理首元素地址(常见方式)
void print_array2(int* p, int n) {
    for(int i = 0; i < n; i++) {
        printf("%d ", p[i]);
    }
}

int main() {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    
    printf("使用数组指针访问:");
    print_array(&arr);  // 必须传&arr
    
    printf("\n使用首元素指针访问:");
    print_array2(arr, 10);  // 传arr
    
    return 0;
}

三、使用指针访问数组 - 全方位解析

1. 指针访问的多种写法

c 复制代码
#include <stdio.h>
int main() {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    int* p = arr;
    int sz = sizeof(arr) / sizeof(arr[0]);
    
    printf("数组元素:\n");
    for (int i = 0; i < sz; i++) {
        // 所有写法等价
        printf("arr[%d] = ", i);
        printf("%d ", arr[i]);        // 标准写法
        printf("%d ", *(arr + i));     // 指针形式
        printf("%d ", *(p + i));       // 用p指针
        printf("%d ", p[i]);            // p[i] 等价于 arr[i]
        printf("%d ", i[arr]);          // 奇特的交换写法
        printf("\n");
    }
    
    // 验证 [] 的本质
    printf("\n汇编角度理解:\n");
    printf("arr[5] = %d\n", arr[5]);
    printf("*(arr+5) = %d\n", *(arr+5));
    printf("5[arr] = %d\n", 5[arr]);
    
    return 0;
}

2. 指针运算的细节

c 复制代码
#include <stdio.h>
int main() {
    int arr[10] = {0};
    int* p = arr;
    
    // 指针加法
    printf("p = %p\n", p);
    printf("p + 1 = %p (加4字节)\n", p + 1);
    printf("p + 2 = %p (加8字节)\n", p + 2);
    
    // 指针减法
    printf("\n指针减法:\n");
    printf("(p+5) - p = %ld (相差5个元素)\n", (p+5) - p);
    
    // 指针比较
    printf("\n指针比较:\n");
    if(p < p+5) {
        printf("p 在 p+5 之前\n");
    }
    
    // 通过指针修改数组
    for(int i = 0; i < 10; i++) {
        *(p + i) = i * 10;  // 赋值:0,10,20,...
    }
    
    printf("\n修改后的数组:");
    for(int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);
    }
    
    return 0;
}

3. 指针访问的边界问题

c 复制代码
#include <stdio.h>
int main() {
    int arr[5] = {1,2,3,4,5};
    int* p = arr;
    
    // 合法访问
    printf("合法访问:\n");
    for(int i = 0; i < 5; i++) {
        printf("%d ", *(p + i));
    }
    
    // 危险访问(越界)
    printf("\n\n危险访问(越界):\n");
    printf("p-1 = %p, 值 = %d (未知)\n", p-1, *(p-1));
    printf("p+5 = %p, 值 = %d (未知)\n", p+5, *(p+5));
    
    // 使用指针访问时一定要确保不越界
    return 0;
}

四、一维数组传参的本质 - 深入剖析

1. 验证传参本质

c 复制代码
#include <stdio.h>

// 不同的形参写法,本质都是指针
void test1(int arr[10]) {
    printf("test1: sizeof(arr) = %d\n", sizeof(arr));  // 4
    printf("test1: arr = %p\n", arr);
}

void test2(int arr[]) {
    printf("test2: sizeof(arr) = %d\n", sizeof(arr));  // 4
    printf("test2: arr = %p\n", arr);
}

void test3(int* arr) {
    printf("test3: sizeof(arr) = %d\n", sizeof(arr));  // 4
    printf("test3: arr = %p\n", arr);
}

int main() {
    int arr[10] = {1,2,3,4,5,6,7,8,9,10};
    
    printf("main中: sizeof(arr) = %d\n", sizeof(arr));  // 40
    printf("main中: arr = %p\n\n", arr);
    
    test1(arr);
    test2(arr);
    test3(arr);
    
    return 0;
}

输出分析:

复制代码
main中: sizeof(arr) = 40
main中: arr = 0060FEF0

test1: sizeof(arr) = 4
test1: arr = 0060FEF0
test2: sizeof(arr) = 4
test2: arr = 0060FEF0
test3: sizeof(arr) = 4
test3: arr = 0060FEF0

2. 为什么必须传递数组长度

c 复制代码
#include <stdio.h>

// 错误的函数:无法知道数组长度
void bad_function(int arr[]) {
    // 无法知道数组实际长度
    int len = sizeof(arr) / sizeof(arr[0]);  // len = 1 (4/4)
    printf("错误计算的长度: %d\n", len);
    
    // 危险!可能越界
    for(int i = 0; i < 10; i++) {
        printf("%d ", arr[i]);  // 如果传入的数组小于10,会越界
    }
}

// 正确的函数:传入长度
void good_function(int arr[], int n) {
    printf("正确的访问: ");
    for(int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr1[10] = {1,2,3,4,5,6,7,8,9,10};
    int arr2[5] = {1,2,3,4,5};
    
    printf("处理arr1:\n");
    bad_function(arr1);      // 危险!
    good_function(arr1, 10); // 正确
    
    printf("\n处理arr2:\n");
    bad_function(arr2);      // 危险!会访问越界
    good_function(arr2, 5);  // 正确
    
    return 0;
}

3. 多维数组传参

c 复制代码
#include <stdio.h>

// 二维数组传参
void print_2d_array(int arr[][3], int rows) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

// 使用数组指针
void print_2d_array2(int (*arr)[3], int rows) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < 3; j++) {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

int main() {
    int arr[2][3] = {{1,2,3}, {4,5,6}};
    
    printf("方式1:\n");
    print_2d_array(arr, 2);
    
    printf("\n方式2:\n");
    print_2d_array2(arr, 2);
    
    return 0;
}

五、冒泡排序和选择排序 - 完整实现与优化

1. 冒泡排序详解

c 复制代码
#include <stdio.h>
#include <stdbool.h>

// 打印数组
void print_array(int arr[], int n) {
    for(int i = 0; i < n; i++) {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

// 基础版冒泡排序
void bubble_sort_basic(int arr[], int n) {
    int compare_count = 0;
    int swap_count = 0;
    
    for(int i = 0; i < n-1; i++) {
        for(int j = 0; j < n-1-i; j++) {
            compare_count++;
            if(arr[j] > arr[j+1]) {
                swap_count++;
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
        printf("第%d趟后: ", i+1);
        print_array(arr, n);
    }
    printf("总比较次数: %d, 总交换次数: %d\n", compare_count, swap_count);
}

// 优化版冒泡排序
void bubble_sort_optimized(int arr[], int n) {
    int compare_count = 0;
    int swap_count = 0;
    
    for(int i = 0; i < n-1; i++) {
        bool swapped = false;  // 标志位
        
        for(int j = 0; j < n-1-i; j++) {
            compare_count++;
            if(arr[j] > arr[j+1]) {
                swapped = true;
                swap_count++;
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
        
        printf("第%d趟后: ", i+1);
        print_array(arr, n);
        
        if(!swapped) {
            printf("第%d趟没有交换,排序完成\n", i+1);
            break;
        }
    }
    printf("总比较次数: %d, 总交换次数: %d\n", compare_count, swap_count);
}

int main() {
    // 测试不同情况
    int arr1[] = {5, 4, 3, 2, 1};  // 逆序
    int arr2[] = {1, 2, 3, 4, 5};  // 已有序
    int arr3[] = {3, 1, 4, 2, 5};  // 乱序
    int n1 = sizeof(arr1)/sizeof(arr1[0]);
    int n2 = sizeof(arr2)/sizeof(arr2[0]);
    int n3 = sizeof(arr3)/sizeof(arr3[0]);
    
    printf("=== 逆序数组 冒泡排序 ===\n");
    bubble_sort_optimized(arr1, n1);
    
    printf("\n=== 有序数组 基础版 ===\n");
    bubble_sort_basic(arr2, n2);
    
    int arr2_2[] = {1, 2, 3, 4, 5};
    printf("\n=== 有序数组 优化版 ===\n");
    bubble_sort_optimized(arr2_2, n2);
    
    printf("\n=== 乱序数组 优化版 ===\n");
    bubble_sort_optimized(arr3, n3);
    
    return 0;
}

2. 选择排序详解

c 复制代码
#include <stdio.h>

// 选择排序实现
void selection_sort(int arr[], int n) {
    int compare_count = 0;
    int swap_count = 0;
    
    for(int i = 0; i < n-1; i++) {
        int min_index = i;  // 假设当前位置是最小值
        
        // 在未排序部分找最小值
        for(int j = i+1; j < n; j++) {
            compare_count++;
            if(arr[j] < arr[min_index]) {
                min_index = j;
            }
        }
        
        // 将最小值交换到当前位置
        if(min_index != i) {
            swap_count++;
            int temp = arr[i];
            arr[i] = arr[min_index];
            arr[min_index] = temp;
        }
        
        printf("第%d次选择后: ", i+1);
        for(int k = 0; k < n; k++) {
            printf("%d ", arr[k]);
        }
        printf(" (选择了arr[%d]=%d)\n", min_index, arr[i]);
    }
    printf("总比较次数: %d, 总交换次数: %d\n\n", compare_count, swap_count);
}

// 双向选择排序(同时找最大和最小)
void double_selection_sort(int arr[], int n) {
    int left = 0, right = n-1;
    int compare_count = 0;
    int swap_count = 0;
    
    while(left < right) {
        int min_index = left;
        int max_index = left;
        
        // 在一趟中同时找最大和最小
        for(int i = left; i <= right; i++) {
            compare_count += 2;
            if(arr[i] < arr[min_index]) {
                min_index = i;
            }
            if(arr[i] > arr[max_index]) {
                max_index = i;
            }
        }
        
        // 交换最小值到左边
        if(min_index != left) {
            swap_count++;
            int temp = arr[left];
            arr[left] = arr[min_index];
            arr[min_index] = temp;
        }
        
        // 如果最大值在left位置,它可能被刚才的交换移走了
        if(max_index == left) {
            max_index = min_index;
        }
        
        // 交换最大值到右边
        if(max_index != right) {
            swap_count++;
            int temp = arr[right];
            arr[right] = arr[max_index];
            arr[max_index] = temp;
        }
        
        left++;
        right--;
        
        printf("第%d次双向选择: ", left);
        for(int k = 0; k < n; k++) {
            printf("%d ", arr[k]);
        }
        printf("\n");
    }
    printf("总比较次数: %d, 总交换次数: %d\n", compare_count, swap_count);
}

int main() {
    int arr1[] = {64, 25, 12, 22, 11};
    int arr2[] = {64, 25, 12, 22, 11};
    int n = sizeof(arr1)/sizeof(arr1[0]);
    
    printf("原始数组: ");
    for(int i = 0; i < n; i++) printf("%d ", arr1[i]);
    printf("\n\n");
    
    printf("=== 普通选择排序 ===\n");
    selection_sort(arr1, n);
    
    printf("\n最终结果: ");
    for(int i = 0; i < n; i++) printf("%d ", arr1[i]);
    
    printf("\n\n=== 双向选择排序 ===\n");
    double_selection_sort(arr2, n);
    
    printf("\n最终结果: ");
    for(int i = 0; i < n; i++) printf("%d ", arr2[i]);
    
    return 0;
}

3. 排序算法对比

c 复制代码
#include <stdio.h>
#include <time.h>

// 冒泡排序
void bubble_sort(int arr[], int n) {
    for(int i = 0; i < n-1; i++) {
        for(int j = 0; j < n-1-i; j++) {
            if(arr[j] > arr[j+1]) {
                int temp = arr[j];
                arr[j] = arr[j+1];
                arr[j+1] = temp;
            }
        }
    }
}

// 选择排序
void selection_sort(int arr[], int n) {
    for(int i = 0; i < n-1; i++) {
        int min = i;
        for(int j = i+1; j < n; j++) {
            if(arr[j] < arr[min]) {
                min = j;
            }
        }
        if(min != i) {
            int temp = arr[i];
            arr[i] = arr[min];
            arr[min] = temp;
        }
    }
}

// 性能测试
void test_performance() {
    const int SIZE = 10000;
    int arr1[SIZE], arr2[SIZE];
    clock_t start, end;
    
    // 初始化随机数组
    for(int i = 0; i < SIZE; i++) {
        arr1[i] = rand() % 10000;
        arr2[i] = arr1[i];
    }
    
    printf("排序 %d 个元素:\n", SIZE);
    
    // 测试冒泡排序
    start = clock();
    bubble_sort(arr1, SIZE);
    end = clock();
    printf("冒泡排序时间: %.3f 秒\n", (double)(end-start)/CLOCKS_PER_SEC);
    
    // 测试选择排序
    start = clock();
    selection_sort(arr2, SIZE);
    end = clock();
    printf("选择排序时间: %.3f 秒\n", (double)(end-start)/CLOCKS_PER_SEC);
}

int main() {
    test_performance();
    return 0;
}

六、二级指针 - 深入理解

1. 二级指针的基本概念

c 复制代码
#include <stdio.h>

int main() {
    int a = 10;
    int* pa = &a;    // 一级指针:指向int变量
    int** ppa = &pa; // 二级指针:指向int*变量
    int*** pppa = &ppa; // 三级指针:指向int**变量
    
    printf("a = %d\n", a);
    printf("&a = %p\n", &a);
    
    printf("\npa = %p\n", pa);
    printf("&pa = %p\n", &pa);
    printf("*pa = %d\n", *pa);
    
    printf("\nppa = %p\n", ppa);
    printf("&ppa = %p\n", &ppa);
    printf("*ppa = %p\n", *ppa);    // 得到pa的值(a的地址)
    printf("**ppa = %d\n", **ppa);  // 得到a的值
    
    printf("\n三级指针:\n");
    printf("***pppa = %d\n", ***pppa);
    
    return 0;
}

内存示意图:

复制代码
内存地址    内容         变量名
0x1000     10           a
0x2000     0x1000       pa
0x3000     0x2000       ppa
0x4000     0x3000       pppa

2. 二级指针的典型应用

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 应用1:在函数中修改一级指针的值
void allocate_memory(int** p, int size) {
    *p = (int*)malloc(size * sizeof(int));
    if(*p != NULL) {
        printf("内存分配成功,地址: %p\n", *p);
    }
}

void test_allocate() {
    int* arr = NULL;
    allocate_memory(&arr, 10);  // 传一级指针的地址
    
    if(arr != NULL) {
        // 使用内存
        for(int i = 0; i < 10; i++) {
            arr[i] = i;
        }
        free(arr);
    }
}

// 应用2:二维数组的动态创建
int** create_2d_array(int rows, int cols) {
    int** matrix = (int**)malloc(rows * sizeof(int*));
    
    for(int i = 0; i < rows; i++) {
        matrix[i] = (int*)malloc(cols * sizeof(int));
    }
    
    return matrix;
}

void free_2d_array(int** matrix, int rows) {
    for(int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
}

// 应用3:指针数组的操作
void print_strings(char** strings, int count) {
    for(int i = 0; i < count; i++) {
        printf("字符串%d: %s (地址: %p)\n", 
               i+1, strings[i], strings[i]);
    }
}

int main() {
    // 测试内存分配
    printf("=== 测试内存分配 ===\n");
    test_allocate();
    
    // 测试二维数组
    printf("\n=== 测试二维数组 ===\n");
    int** matrix = create_2d_array(3, 4);
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 4; j++) {
            matrix[i][j] = i * 4 + j;
        }
    }
    free_2d_array(matrix, 3);
    
    // 测试字符串数组
    printf("\n=== 测试字符串数组 ===\n");
    char* fruits[] = {"苹果", "香蕉", "橙子"};
    print_strings(fruits, 3);
    
    return 0;
}

3. 多级指针的灵活运用

c 复制代码
#include <stdio.h>

void demonstrate_multi_level() {
    int x = 100;
    int* p1 = &x;
    int** p2 = &p1;
    int*** p3 = &p2;
    int**** p4 = &p3;
    
    printf("x = %d\n", x);
    printf("*p1 = %d\n", *p1);
    printf("**p2 = %d\n", **p2);
    printf("***p3 = %d\n", ***p3);
    printf("****p4 = %d\n", ****p4);
    
    // 修改值
    ****p4 = 200;
    printf("\n修改后:\n");
    printf("x = %d\n", x);
}

int main() {
    demonstrate_multi_level();
    return 0;
}

七、指针数组 - 完整解析

1. 指针数组的基本概念

c 复制代码
#include <stdio.h>

int main() {
    // 定义指针数组的多种方式
    int a = 10, b = 20, c = 30;
    
    // 方式1:直接初始化
    int* parr1[3] = {&a, &b, &c};
    
    // 方式2:先定义后赋值
    int* parr2[3];
    parr2[0] = &a;
    parr2[1] = &b;
    parr2[2] = &c;
    
    // 访问方式
    printf("通过指针数组访问变量:\n");
    for(int i = 0; i < 3; i++) {
        printf("parr1[%d] = %p, 指向的值 = %d\n", 
               i, parr1[i], *parr1[i]);
    }
    
    // 指针数组的内存布局
    printf("\n内存布局:\n");
    printf("parr1数组起始地址: %p\n", parr1);
    printf("parr1[0]地址: %p, 内容: %p\n", &parr1[0], parr1[0]);
    printf("parr1[1]地址: %p, 内容: %p\n", &parr1[1], parr1[1]);
    printf("parr1[2]地址: %p, 内容: %p\n", &parr1[2], parr1[2]);
    
    return 0;
}

2. 不同类型指针数组

c 复制代码
#include <stdio.h>

int main() {
    // 1. int* 类型的指针数组
    int int_arr[] = {1,2,3};
    int* int_ptrs[3];
    for(int i = 0; i < 3; i++) {
        int_ptrs[i] = &int_arr[i];
    }
    
    // 2. char* 类型的指针数组(字符串数组)
    char* str_ptrs[3] = {
        "Hello",
        "World",
        "Pointer"
    };
    
    // 3. double* 类型的指针数组
    double d1 = 3.14, d2 = 2.718, d3 = 1.414;
    double* d_ptrs[3] = {&d1, &d2, &d3};
    
    // 4. void* 类型的指针数组(可以指向任何类型)
    void* v_ptrs[3];
    v_ptrs[0] = &d1;
    v_ptrs[1] = str_ptrs[0];
    v_ptrs[2] = &int_arr[0];
    
    printf("字符串指针数组内容:\n");
    for(int i = 0; i < 3; i++) {
        printf("str_ptrs[%d] = %s\n", i, str_ptrs[i]);
    }
    
    return 0;
}

八、指针数组格式书写举例

1. 各种指针数组定义示例

c 复制代码
#include <stdio.h>

// 定义一些数据供指针指向
int global_a = 100;
int global_b = 200;
double global_pi = 3.14159;
char global_ch = 'Z';

int main() {
    // 示例1:指向局部变量的指针数组
    int local_x = 10, local_y = 20, local_z = 30;
    int* ptr_to_locals[3] = {&local_x, &local_y, &local_z};
    
    // 示例2:指向全局变量的指针数组
    int* ptr_to_globals[2] = {&global_a, &global_b};
    
    // 示例3:指向不同类型变量的指针数组
    void* mixed_ptrs[3] = {&global_a, &global_pi, &global_ch};
    
    // 示例4:指向数组的指针数组
    int arr1[] = {1,2,3};
    int arr2[] = {4,5,6};
    int arr3[] = {7,8,9};
    int* ptr_to_arrays[3] = {arr1, arr2, arr3};
    
    // 示例5:函数指针数组(高级)
    // int (*func_ptrs[3])(int, int);
    
    // 示例6:指向指针的指针数组
    int** ptr_to_ptrs[3];
    ptr_to_ptrs[0] = &ptr_to_locals[0];
    ptr_to_ptrs[1] = &ptr_to_locals[1];
    ptr_to_ptrs[2] = &ptr_to_locals[2];
    
    // 打印验证
    printf("指向数组的指针数组:\n");
    for(int i = 0; i < 3; i++) {
        printf("ptr_to_arrays[%d] 指向数组首元素 %d\n", 
               i, ptr_to_arrays[i][0]);
    }
    
    return 0;
}

2. 复杂指针数组声明解析

c 复制代码
#include <stdio.h>

int main() {
    // 声明1:int* 数组
    int* a[5];  // a是一个数组,包含5个int*元素
    
    // 声明2:const int* 数组
    const int* b[5];  // b是数组,元素指向const int
    
    // 声明3:int* const 数组
    int* const c[5] = {NULL};  // c是数组,元素是const指针(必须初始化)
    
    // 声明4:指向char数组的指针数组
    char* d[5];  // 常用作字符串数组
    
    // 声明5:二维指针数组
    int** e[5];  // e是数组,元素是int**
    
    // 声明6:函数指针数组
    int (*f[5])(int, int);  // f是数组,元素是函数指针
    
    printf("各种指针数组声明示例\n");
    printf("sizeof(a) = %d (5个int*)\n", sizeof(a));
    printf("sizeof(d) = %d (5个char*)\n", sizeof(d));
    
    return 0;
}

九、指针数组模拟二维数组 - 深入剖析

1. 基本原理和内存布局

c 复制代码
#include <stdio.h>

int main() {
    // 三个一维数组
    int row0[4] = {1, 2, 3, 4};
    int row1[4] = {5, 6, 7, 8};
    int row2[4] = {9, 10, 11, 12};
    
    // 指针数组模拟二维数组
    int* matrix[3] = {row0, row1, row2};
    
    printf("内存布局分析:\n");
    printf("matrix数组地址: %p\n", matrix);
    
    for(int i = 0; i < 3; i++) {
        printf("matrix[%d] = %p (指向row%d首地址)\n", i, matrix[i], i);
        printf("  row%d实际地址: %p\n", i, 
               i==0 ? (void*)row0 : (i==1 ? (void*)row1 : (void*)row2));
    }
    
    printf("\n模拟二维数组访问:\n");
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 4; j++) {
            // 四种访问方式
            printf("matrix[%d][%d] = ", i, j);
            printf("%d ", matrix[i][j]);        // 方式1
            printf("%d ", *(matrix[i] + j));     // 方式2
            printf("%d ", *(*(matrix + i) + j)); // 方式3
            printf("\n");
        }
    }
    
    return 0;
}

2. 与真实二维数组的对比

c 复制代码
#include <stdio.h>

int main() {
    // 真实二维数组(内存连续)
    int real_2d[3][4] = {
        {1,2,3,4},
        {5,6,7,8},
        {9,10,11,12}
    };
    
    // 指针数组模拟(内存可能不连续)
    int row1[4] = {1,2,3,4};
    int row2[4] = {5,6,7,8};
    int row3[4] = {9,10,11,12};
    int* simulated_2d[3] = {row1, row2, row3};
    
    printf("=== 内存布局对比 ===\n");
    
    printf("\n真实二维数组内存:\n");
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 4; j++) {
            printf("%p ", &real_2d[i][j]);
        }
        printf("\n");
    }
    
    printf("\n模拟二维数组内存:\n");
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 4; j++) {
            printf("%p ", &simulated_2d[i][j]);
        }
        printf("\n");
    }
    
    printf("\n=== 访问方式对比 ===\n");
    printf("真实二维数组的大小: %d\n", sizeof(real_2d));
    printf("模拟二维数组的大小: %d\n", sizeof(simulated_2d));
    
    return 0;
}

3. 动态创建和操作

c 复制代码
#include <stdio.h>
#include <stdlib.h>

// 动态创建指针数组模拟的二维矩阵
int** create_matrix(int rows, int cols) {
    // 分配指针数组
    int** matrix = (int**)malloc(rows * sizeof(int*));
    if(matrix == NULL) return NULL;
    
    // 为每一行分配空间
    for(int i = 0; i < rows; i++) {
        matrix[i] = (int*)malloc(cols * sizeof(int));
        if(matrix[i] == NULL) {
            // 分配失败,释放已分配的内存
            for(int j = 0; j < i; j++) {
                free(matrix[j]);
            }
            free(matrix);
            return NULL;
        }
    }
    return matrix;
}

// 初始化矩阵
void init_matrix(int** matrix, int rows, int cols) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < cols; j++) {
            matrix[i][j] = i * cols + j + 1;
        }
    }
}

// 打印矩阵
void print_matrix(int** matrix, int rows, int cols) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < cols; j++) {
            printf("%4d ", matrix[i][j]);
        }
        printf("\n");
    }
}

// 释放矩阵
void free_matrix(int** matrix, int rows) {
    for(int i = 0; i < rows; i++) {
        free(matrix[i]);
    }
    free(matrix);
}

// 矩阵转置
int** transpose_matrix(int** matrix, int rows, int cols) {
    int** result = create_matrix(cols, rows);
    if(result == NULL) return NULL;
    
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < cols; j++) {
            result[j][i] = matrix[i][j];
        }
    }
    return result;
}

int main() {
    int rows = 3, cols = 4;
    
    // 创建矩阵
    int** mat = create_matrix(rows, cols);
    if(mat == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 初始化并打印
    init_matrix(mat, rows, cols);
    printf("原始矩阵:\n");
    print_matrix(mat, rows, cols);
    
    // 转置
    int** trans = transpose_matrix(mat, rows, cols);
    printf("\n转置矩阵:\n");
    print_matrix(trans, cols, rows);
    
    // 释放内存
    free_matrix(mat, rows);
    free_matrix(trans, cols);
    
    return 0;
}

十、字符指针变量 - 全方位解析

1. 字符指针的基础用法

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // 1. 指向单个字符
    char ch = 'A';
    char* pc = &ch;
    
    printf("指向单个字符:\n");
    printf("ch = %c\n", ch);
    printf("*pc = %c\n", *pc);
    
    *pc = 'B';  // 通过指针修改
    printf("修改后 ch = %c\n", ch);
    
    // 2. 指向字符串(字符数组)
    char str[] = "Hello";
    char* pstr = str;  // 或 char* pstr = &str[0];
    
    printf("\n指向字符数组:\n");
    printf("str = %s\n", str);
    printf("pstr = %s\n", pstr);
    
    // 通过指针修改字符数组
    pstr[0] = 'h';
    printf("修改后 str = %s\n", str);
    
    // 3. 指向常量字符串
    const char* pconst = "World";  // 推荐加const
    printf("\n指向常量字符串:\n");
    printf("pconst = %s\n", pconst);
    // pconst[0] = 'w';  // 错误!不能修改常量字符串
    
    return 0;
}

2. 字符指针的运算

c 复制代码
#include <stdio.h>

int main() {
    const char* str = "Hello, World!";
    const char* p = str;
    
    printf("字符串: %s\n", str);
    printf("逐个字符输出:\n");
    
    // 使用指针遍历字符串
    while(*p != '\0') {
        printf("当前字符: %c, 地址: %p\n", *p, p);
        p++;  // 指针后移
    }
    
    printf("字符串长度: %d\n", p - str);  // 指针减法得到长度
    
    // 指针的比较
    p = str;
    const char* q = str + 7;  // 指向'W'
    
    if(p < q) {
        printf("p 在 q 之前\n");
    }
    
    // 查找字符
    while(*p != '\0' && *p != 'W') {
        p++;
    }
    if(*p == 'W') {
        printf("找到字符 'W' 在偏移 %d 位置\n", p - str);
    }
    
    return 0;
}

3. 字符指针数组 - 字符串数组

c 复制代码
#include <stdio.h>

int main() {
    // 方式1:使用字符指针数组存储字符串
    const char* weekdays[] = {
        "Monday",
        "Tuesday",
        "Wednesday",
        "Thursday",
        "Friday",
        "Saturday",
        "Sunday"
    };
    
    int num_days = sizeof(weekdays) / sizeof(weekdays[0]);
    
    printf("一周七天:\n");
    for(int i = 0; i < num_days; i++) {
        printf("星期%d: %s (地址: %p)\n", 
               i+1, weekdays[i], weekdays[i]);
    }
    
    // 方式2:二维字符数组(内存连续)
    char weekdays2[7][10] = {
        "Monday", "Tuesday", "Wednesday",
        "Thursday", "Friday", "Saturday", "Sunday"
    };
    
    printf("\n二维字符数组内存布局:\n");
    for(int i = 0; i < 7; i++) {
        printf("weekdays2[%d] = %s (地址: %p)\n", 
               i, weekdays2[i], weekdays2[i]);
    }
    
    // 对比两种方式的区别
    printf("\n区别:\n");
    printf("weekdays[0] 指向: %p\n", weekdays[0]);
    printf("weekdays[1] 指向: %p\n", weekdays[1]);
    printf("两个地址不一定连续\n");
    
    printf("weekdays2[0] 地址: %p\n", weekdays2[0]);
    printf("weekdays2[1] 地址: %p\n", weekdays2[1]);
    printf("相差10字节(连续)\n");
    
    return 0;
}

十一、不可以修改常量字符串的值 - 深入理解

1. 为什么不能修改

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    // 情况1:字符数组 - 可修改
    char arr[] = "Hello";
    printf("字符数组(栈区):%s\n", arr);
    arr[0] = 'h';  // 可以修改
    printf("修改后:%s\n", arr);
    
    // 情况2:常量字符串 - 不可修改
    char* p = "Hello";
    printf("\n常量字符串(只读数据区):%s\n", p);
    // p[0] = 'h';  // 运行时错误!段错误
    
    // 验证内存位置
    printf("\n内存地址对比:\n");
    printf("arr的地址: %p\n", arr);
    printf("p指向的地址: %p\n", p);
    
    // 情况3:使用const保护
    const char* cp = "Hello";
    // cp[0] = 'h';  // 编译错误
    
    // 情况4:动态分配内存 - 可修改
    char* heap_str = (char*)malloc(10);
    strcpy(heap_str, "Hello");
    printf("\n堆区字符串:%s\n", heap_str);
    heap_str[0] = 'h';  // 可以修改
    printf("修改后:%s\n", heap_str);
    free(heap_str);
    
    return 0;
}

2. 内存分区详解

c 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 全局变量区
char global_arr[] = "Global";        // 数据区
const char* global_ptr = "GlobalConst";  // 指针在数据区,指向只读数据区

int main() {
    // 栈区
    char stack_arr[] = "Stack";
    
    // 只读数据区
    const char* rodata_ptr = "ReadOnly";
    
    // 堆区
    char* heap_str = (char*)malloc(20);
    strcpy(heap_str, "Heap");
    
    printf("=== 不同内存区域的字符串 ===\n\n");
    
    printf("栈区字符串:\n");
    printf("  地址: %p\n", stack_arr);
    printf("  内容: %s\n", stack_arr);
    printf("  可修改: %s\n\n", "是");
    
    printf("全局数据区字符串:\n");
    printf("  地址: %p\n", global_arr);
    printf("  内容: %s\n", global_arr);
    printf("  可修改: %s\n\n", "是");
    
    printf("只读数据区字符串:\n");
    printf("  地址: %p\n", rodata_ptr);
    printf("  内容: %s\n", rodata_ptr);
    printf("  可修改: %s\n\n", "否");
    
    printf("堆区字符串:\n");
    printf("  地址: %p\n", heap_str);
    printf("  内容: %s\n", heap_str);
    printf("  可修改: %s\n", "是");
    
    free(heap_str);
    return 0;
}

十二、《剑指offer》字符串笔试题 - 详细解析

1. 原题解析

c 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str1[] = "hello bit.";
    char str2[] = "hello bit.";
    const char *str3 = "hello bit.";
    const char *str4 = "hello bit.";
    
    printf("=== 地址比较 ===\n");
    printf("str1 = %p\n", str1);
    printf("str2 = %p\n", str2);
    printf("str3 = %p\n", str3);
    printf("str4 = %p\n\n", str4);
    
    printf("=== 内存布局分析 ===\n");
    printf("str1数组位于栈区:\n");
    for(int i = 0; i <= strlen(str1); i++) {
        printf("  %p: '%c' (%d)\n", &str1[i], str1[i], str1[i]);
    }
    
    printf("\nstr2数组位于栈区(不同位置):\n");
    for(int i = 0; i <= strlen(str2); i++) {
        printf("  %p: '%c' (%d)\n", &str2[i], str2[i], str2[i]);
    }
    
    printf("\nstr3指向只读数据区:\n");
    printf("  str3 = %p\n", str3);
    printf("  str3指向的内容:%s\n", str3);
    
    if(str1 == str2)
        printf("\nstr1 and str2 are same\n");
    else
        printf("\nstr1 and str2 are not same\n");  // 输出这一行
    
    if(str3 == str4)
        printf("str3 and str4 are same\n");      // 输出这一行
    else
        printf("str3 and str4 are not same\n");
    
    return 0;
}

2. 深入扩展

c 复制代码
#include <stdio.h>
#include <string.h>

// 验证字符串常量池
void test_string_pool() {
    const char* s1 = "Hello";
    const char* s2 = "Hello";
    const char* s3 = "World";
    
    char arr1[] = "Hello";
    char arr2[] = "Hello";
    
    printf("=== 字符串常量池测试 ===\n");
    printf("s1 = %p\n", s1);
    printf("s2 = %p\n", s2);
    printf("s3 = %p\n", s3);
    printf("arr1 = %p\n", arr1);
    printf("arr2 = %p\n\n", arr2);
    
    if(s1 == s2) {
        printf("s1和s2指向同一块内存(常量池优化)\n");
    }
    
    if(arr1 != arr2) {
        printf("arr1和arr2是不同的数组(栈上不同位置)\n");
    }
}

// 字符串比较的正确方式
void correct_comparison() {
    char str1[] = "hello";
    char str2[] = "hello";
    const char* str3 = "hello";
    const char* str4 = "hello";
    
    printf("\n=== 正确比较字符串内容 ===\n");
    
    // 错误:比较地址
    printf("错误比较(比较地址):\n");
    if(str1 == str2) {
        printf("str1 == str2\n");
    } else {
        printf("str1 != str2\n");
    }
    
    // 正确:使用strcmp比较内容
    printf("正确比较(使用strcmp):\n");
    if(strcmp(str1, str2) == 0) {
        printf("str1 和 str2 内容相同\n");
    }
    
    if(strcmp(str3, str4) == 0) {
        printf("str3 和 str4 内容相同\n");
    }
}

int main() {
    test_string_pool();
    correct_comparison();
    return 0;
}

十三、数组指针 - 完整精讲

1. 数组指针的基本概念

c 复制代码
#include <stdio.h>

int main() {
    // 1. 定义数组指针的语法
    int arr[5] = {1, 2, 3, 4, 5};
    
    // int* p = arr;        // 指向首元素
    int (*pa)[5] = &arr;    // 指向整个数组
    
    printf("arr = %p\n", arr);
    printf("&arr = %p\n", &arr);
    printf("pa = %p\n", pa);
    
    // 2. 使用数组指针访问元素
    printf("\n使用数组指针访问:\n");
    for(int i = 0; i < 5; i++) {
        printf("(*pa)[%d] = %d\n", i, (*pa)[i]);
    }
    
    // 3. 数组指针的运算
    printf("\n数组指针运算:\n");
    printf("pa = %p\n", pa);
    printf("pa + 1 = %p (跳过一个数组)\n", pa + 1);
    
    // 4. 对比普通指针
    int* p = arr;
    printf("\n普通指针运算:\n");
    printf("p = %p\n", p);
    printf("p + 1 = %p (跳过一个元素)\n", p + 1);
    
    return 0;
}

2. 数组指针的声明解析

c 复制代码
#include <stdio.h>

int main() {
    // 声明解析练习
    int a[5];              // a: 包含5个int的数组
    int* b[5];             // b: 包含5个int*的数组(指针数组)
    int (*c)[5];           // c: 指向包含5个int的数组的指针(数组指针)
    int (*d[5])[3];        // d: 包含5个元素的数组,每个元素是指向包含3个int的数组的指针
    
    printf("各种声明的大小:\n");
    printf("sizeof(a) = %d\n", sizeof(a));      // 20
    printf("sizeof(b) = %d\n", sizeof(b));      // 20 (5个指针)
    printf("sizeof(c) = %d\n", sizeof(c));      // 4 (一个指针)
    printf("sizeof(d) = %d\n", sizeof(d));      // 20 (5个指针)
    
    // 复杂声明的解析技巧:右左法则
    // 1. int (*c)[5]  -- c是指针,指向int[5]
    // 2. int *d[5]    -- d是数组,元素是int*
    // 3. int (*e[5])[3] -- e是数组,元素是指针,指向int[3]
    
    return 0;
}

3. 数组指针的应用场景

c 复制代码
#include <stdio.h>

// 场景1:二维数组传参
void print_2d_array(int (*arr)[4], int rows) {
    for(int i = 0; i < rows; i++) {
        for(int j = 0; j < 4; j++) {
            printf("%d ", arr[i][j]);  // arr[i][j] 等价于 *(*(arr+i)+j)
        }
        printf("\n");
    }
}

// 场景2:返回数组指针
int (*create_array())[5] {
    static int arr[5] = {1,2,3,4,5};
    return &arr;
}

// 场景3:数组指针数组
void demonstrate_array_of_array_pointers() {
    int arr1[3] = {1,2,3};
    int arr2[3] = {4,5,6};
    int arr3[3] = {7,8,9};
    
    // 数组指针数组:每个元素指向一个包含3个int的数组
    int (*arr_ptrs[3])[3] = {&arr1, &arr2, &arr3};
    
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 3; j++) {
            printf("%d ", (*arr_ptrs[i])[j]);
        }
        printf("\n");
    }
}

// 场景4:动态分配二维数组
void dynamic_2d_array() {
    int (*matrix)[4] = malloc(3 * sizeof(int[4]));  // 分配3行4列的二维数组
    
    if(matrix != NULL) {
        // 初始化
        for(int i = 0; i < 3; i++) {
            for(int j = 0; j < 4; j++) {
                matrix[i][j] = i * 4 + j + 1;
            }
        }
        
        // 打印
        printf("动态分配的二维数组:\n");
        for(int i = 0; i < 3; i++) {
            for(int j = 0; j < 4; j++) {
                printf("%3d ", matrix[i][j]);
            }
            printf("\n");
        }
        
        free(matrix);
    }
}

int main() {
    // 场景1测试
    int arr[3][4] = {
        {1,2,3,4},
        {5,6,7,8},
        {9,10,11,12}
    };
    printf("场景1:二维数组传参\n");
    print_2d_array(arr, 3);
    
    // 场景2测试
    printf("\n场景2:返回数组指针\n");
    int (*p)[5] = create_array();
    for(int i = 0; i < 5; i++) {
        printf("%d ", (*p)[i]);
    }
    printf("\n");
    
    // 场景3测试
    printf("\n场景3:数组指针数组\n");
    demonstrate_array_of_array_pointers();
    
    // 场景4测试
    printf("\n场景4:动态分配\n");
    dynamic_2d_array();
    
    return 0;
}

4. 数组指针与指针数组对比总结

c 复制代码
#include <stdio.h>

int main() {
    // 指针数组 vs 数组指针 全面对比
    
    // 定义数据
    int arr[3][4] = {
        {1,2,3,4},
        {5,6,7,8},
        {9,10,11,12}
    };
    
    // 1. 指针数组:存放每一行的首地址
    int* parr[3] = {arr[0], arr[1], arr[2]};
    
    // 2. 数组指针:指向二维数组的一行
    int (*parr2)[4] = arr;  // 指向第一行
    
    printf("=== 指针数组 vs 数组指针 ===\n\n");
    
    printf("指针数组 parr[3]:\n");
    printf("  parr类型: %s\n", "int* [3]");
    printf("  sizeof(parr) = %d\n", sizeof(parr));
    printf("  访问方式: parr[i][j]\n");
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 4; j++) {
            printf("%d ", parr[i][j]);
        }
        printf("\n");
    }
    
    printf("\n数组指针 parr2:\n");
    printf("  parr2类型: %s\n", "int (*)[4]");
    printf("  sizeof(parr2) = %d\n", sizeof(parr2));
    printf("  访问方式: parr2[i][j] 或 (*(parr2+i))[j]\n");
    for(int i = 0; i < 3; i++) {
        for(int j = 0; j < 4; j++) {
            printf("%d ", parr2[i][j]);
        }
        printf("\n");
    }
    
    // 指针运算对比
    printf("\n指针运算对比:\n");
    printf("parr + 1 = %p (指向parr[1])\n", parr + 1);
    printf("parr2 + 1 = %p (指向arr[1])\n", parr2 + 1);
    
    return 0;
}

总结:指针核心概念速查表

概念 定义格式 本质 内存大小(x86) 典型用法
普通指针 int* p 指向int变量 4字节 p = &a
数组名 int arr[5] 首元素地址 20字节(整个数组) arr[0]
指针数组 int* p[5] 数组,元素是指针 20字节(5个指针) p[0] = &a
数组指针 int (*p)[5] 指针,指向数组 4字节 p = &arr
二级指针 int** p 指向指针的指针 4字节 p = &pa
字符指针 char* p 指向字符或字符串 4字节 p = "hello"
函数指针 int (*p)(int) 指向函数 4字节 p = &func
相关推荐
im_AMBER2 小时前
Leetcode 124 二叉搜索树的最小绝对差 | 二叉树的锯齿形层序遍历
数据结构·学习·算法·leetcode·二叉树
ADDDDDD_Trouvaille2 小时前
2026.2.14——OJ78-82题
c++·算法
Hag_202 小时前
LeetCode Hot100 560.和为K的子数组
数据结构·算法·leetcode
田里的水稻2 小时前
FA_规划和控制(PC)-规律路图法(PRM)
人工智能·算法·机器学习·机器人·自动驾驶
追随者永远是胜利者2 小时前
(LeetCode-Hot100)23. 合并 K 个升序链表
java·算法·leetcode·链表·go
ab1515172 小时前
2.16完成107、108、111
算法
小O的算法实验室2 小时前
2026年IEEE IOTJ SCI2区TOP,面向关键节点感知的灾害区域无人机集群路径规划,深度解析+性能实测
算法·无人机·论文复现·智能算法·智能算法改进
闻缺陷则喜何志丹2 小时前
【构造】P9215 [入门赛 #11] [yLOI2021] 扶苏与 1 (Hard Version)|普及+
c++·算法·洛谷·构造
Neil今天也要学习3 小时前
永磁同步电机控制算法--基于数据驱动的超局部无模型预测电流控制MFPC及改进
单片机·嵌入式硬件·算法