C语言指针全面解析:从内存管理到高级应用

前言

指针是C语言的灵魂,也是最具挑战性的核心概念。理解指针不仅关乎语法掌握,更关系到对计算机内存模型的深刻认知。本文将系统性地解析指针的各个方面,即使是看似与指针无关的冒泡排序,我们也会揭示其与指针的内在联系。

目录

前言

正文

[1. 内存和地址](#1. 内存和地址)

[2. 指针变量和地址](#2. 指针变量和地址)

[3. 指针变量类型的意义](#3. 指针变量类型的意义)

[4. const修饰指针](#4. const修饰指针)

[5. 指针运算](#5. 指针运算)

[6. 野指针](#6. 野指针)

[7. assert断言](#7. assert断言)

[8. 指针的使用和传址调用](#8. 指针的使用和传址调用)

[9. 数组名的理解和使用指针访问数组](#9. 数组名的理解和使用指针访问数组)

[10. 一维数组传参的本质](#10. 一维数组传参的本质)

[11. 冒泡排序的指针分析](#11. 冒泡排序的指针分析)

[12. 二级指针](#12. 二级指针)

[13. 指针数组](#13. 指针数组)

[14. 指针数组模拟二维数组](#14. 指针数组模拟二维数组)

[15. 字符指针变量](#15. 字符指针变量)

[16. 数组指针变量](#16. 数组指针变量)

[17. 二维数组传参的本质](#17. 二维数组传参的本质)

[18. 函数指针变量](#18. 函数指针变量)

[19. 函数指针数组](#19. 函数指针数组)

[20. 转移表](#20. 转移表)

[21. 回调函数](#21. 回调函数)

[22. qsort使用举例](#22. qsort使用举例)

[23. qsort函数的模拟实现](#23. qsort函数的模拟实现)

[24. sizeof和strlen的对比](#24. sizeof和strlen的对比)

[25. 数组和指针笔试题解析](#25. 数组和指针笔试题解析)

[26. 指针运算笔试题解析](#26. 指针运算笔试题解析)

总结


正文

1. 内存和地址

内存本质:计算机内存是由无数个以字节为单位内存单元组成的线性空间,每个单元都有唯一的地址标识。

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

int main() 
{
    int a = 10;
    printf("变量a的值: %d\n", a);
    printf("变量a的地址: %p\n", &a);
    
    // 内存地址的十六进制表示
    // 例如:0x7ffd42a1b23c
    return 0;
}

关键理解:变量名是给程序员使用的标签,编译器会将其转换为内存地址。

2. 指针变量和地址

指针变量专门用于存储内存地址。

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

int main() 
{
    int num = 42;
    int *ptr = &num;  // ptr是指向int的指针变量
    
    printf("num的值: %d\n", num);
    printf("num的地址: %p\n", &num);
    printf("ptr存储的地址: %p\n", ptr);
    printf("通过ptr访问的值: %d\n", *ptr);
    
    *ptr = 100;  // 通过指针修改变量值
    printf("修改后num的值: %d\n", num);
    
    return 0;
}

3. 指针变量类型的意义

指针类型决定了:

  • 解引用时访问的字节数

  • 指针运算的步长

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

int main() 
{
    int arr[5] = {10, 20, 30, 40, 50};
    int *int_ptr = arr;
    char *char_ptr = (char*)arr;
    
    printf("int指针: %p -> ", int_ptr);
    printf("值: %d\n", *int_ptr);
    
    printf("char指针: %p -> ", char_ptr);
    printf("值: %d\n", *char_ptr);
    
    // 指针运算演示类型差异
    printf("\n指针+1操作:\n");
    printf("int_ptr + 1 = %p (步长%d字节)\n", int_ptr + 1, (int)((char*)(int_ptr + 1) - (char*)int_ptr));
    printf("char_ptr + 1 = %p (步长%d字节)\n", char_ptr + 1, (int)(char_ptr + 1 - char_ptr));
    
    return 0;
}

4. const修饰指针

const与指针的三种组合方式:

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

int main() 
{
    int a = 10, b = 20;
    
    // 1. 指向常量的指针 - 不能通过指针修改数据
    const int *ptr1 = &a;
    // *ptr1 = 30;  // 错误:不能修改
    ptr1 = &b;     // 正确:可以改变指向
    
    // 2. 常量指针 - 不能改变指针的指向
    int *const ptr2 = &a;
    *ptr2 = 30;    // 正确:可以修改数据
    // ptr2 = &b;  // 错误:不能改变指向
    
    // 3. 指向常量的常量指针
    const int *const ptr3 = &a;
    // *ptr3 = 40;  // 错误
    // ptr3 = &b;   // 错误
    
    return 0;
}

5. 指针运算

指针支持多种运算操作:

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

int main()
 {
    int arr[] = {10, 20, 30, 40, 50};
    int *ptr = arr;
    int *ptr_end = arr + 4;
    
    printf("数组元素:\n");
    for(int *p = arr; p <= ptr_end; p++) 
    {
        printf("arr[%ld] = %d, 地址: %p\n", p - arr, *p, p);
    }
    
    // 指针关系运算
    printf("\n指针比较:\n");
    printf("ptr < ptr_end: %d\n", ptr < ptr_end);
    printf("ptr_end - ptr: %ld\n", ptr_end - ptr);
    
    return 0;
}

6. 野指针

野指针指向无效内存区域,是常见错误来源:

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

int main() 
{
    // 1. 未初始化的指针
    int *wild_ptr1;
    // printf("%d\n", *wild_ptr1);  // 未定义行为
    
    // 2. 已释放的指针
    int *ptr = (int*)malloc(sizeof(int));
    *ptr = 100;
    free(ptr);  // ptr成为野指针
    // *ptr = 200;  // 危险操作
    
    // 3. 越界访问
    int arr[3] = {1, 2, 3};
    int *wild_ptr3 = arr + 5;  // 越界
    // printf("%d\n", *wild_ptr3);  // 未定义行为
    
    // 正确做法:释放后置为NULL
    ptr = NULL;
    
    return 0;
}

7. assert断言

assert用于调试阶段检查假设条件:

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

int* create_array(int size) 
{
    assert(size > 0 && "数组大小必须为正数");
    int *arr = (int*)malloc(size * sizeof(int));
    assert(arr != NULL && "内存分配失败");
    return arr;
}

int main() 
{
    int *arr = create_array(5);
    
    // 使用数组...
    
    free(arr);
    return 0;
}

8. 指针的使用和传址调用

理解值传递与地址传递的区别:

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

// 值传递 - 无法修改实参
void swap_by_value(int a, int b) 
{
    int temp = a;
    a = b;
    b = temp;
}

// 地址传递 - 可以修改实参
void swap_by_pointer(int *a, int *b) 
{
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() 
{
    int x = 10, y = 20;
    
    printf("交换前: x = %d, y = %d\n", x, y);
    swap_by_value(x, y);
    printf("值传递后: x = %d, y = %d\n", x, y);
    
    swap_by_pointer(&x, &y);
    printf("地址传递后: x = %d, y = %d\n", x, y);
    
    return 0;
}

9. 数组名的理解和使用指针访问数组

数组名在多数情况下退化为指针:

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

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    printf("数组名作为指针:\n");
    printf("arr = %p\n", arr);
    printf("&arr[0] = %p\n", &arr[0]);
    
    // 三种访问数组元素的方式
    printf("\n访问数组元素:\n");
    for(int i = 0; i < 5; i++) {
        printf("arr[%d] = %d, ", i, arr[i]);
        printf("*(arr + %d) = %d, ", i, *(arr + i));
        
        int *ptr = arr;
        printf("ptr[%d] = %d\n", i, ptr[i]);
    }
    
    // 数组名与指针的区别
    printf("\n大小信息:\n");
    printf("sizeof(arr) = %zu (整个数组大小)\n", sizeof(arr));
    printf("sizeof(ptr) = %zu (指针大小)\n", sizeof(int*));
    
    return 0;
}

10. 一维数组传参的本质

数组传参时退化为指针:

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

// 三种等价的函数声明方式
void print_array1(int arr[], int size) 
{
    for(int i = 0; i < size; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void print_array2(int *arr, int size) 
{
    for(int i = 0; i < size; i++) 
    {
        printf("%d ", *(arr + i));
    }
    printf("\n");
}

void print_array3(int arr[5], int size) 
{  // 5被编译器忽略
    for(int i = 0; i < size; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() {
    int arr[5] = {1, 2, 3, 4, 5};
    
    printf("数组传参演示:\n");
    print_array1(arr, 5);
    print_array2(arr, 5);
    print_array3(arr, 5);
    
    // 验证数组退化为指针
    printf("函数内sizeof(arr): ");
    void check_size(int param[])
    {
        printf("%zu\n", sizeof(param));  // 输出指针大小,不是数组大小
    }
    check_size(arr);
    
    return 0;
}

11. 冒泡排序的指针分析

冒泡排序与指针的深度结合:

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

// 传统数组下标版本
void bubble_sort_traditional(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 bubble_sort_pointer(int *arr, int n) 
{
    int *end = arr + n - 1;  // 指向最后一个元素
    
    for(int *i = arr; i < end; i++) 
    {
        for(int *j = arr; j < end - (i - arr); j++) 
        {
            if(*j > *(j + 1)) 
            {
                // 指针交换
                int temp = *j;
                *j = *(j + 1);
                *(j + 1) = temp;
            }
        }
    }
}

// 优化的指针版本
void bubble_sort_optimized(int *arr, int n) 
{
    int *start = arr;
    int *end = arr + n - 1;
    int swapped;
    
    do 
    {
        swapped = 0;
        int *current = start;
        
        while(current < end) 
        {
            if(*current > *(current + 1)) 
            {
                // 交换
                int temp = *current;
                *current = *(current + 1);
                *(current + 1) = temp;
                swapped = 1;
            }
            current++;
        }
        end--;  // 每次循环后,末尾元素已排序
        
    } while(swapped && start < end);
}

void print_array(int *arr, int n, const char *name) 
{
    printf("%s: ", name);
    for(int i = 0; i < n; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() 
{
    int arr1[] = {64, 34, 25, 12, 22, 11, 90};
    int arr2[] = {64, 34, 25, 12, 22, 11, 90};
    int arr3[] = {64, 34, 25, 12, 22, 11, 90};
    int n = sizeof(arr1) / sizeof(arr1[0]);
    
    printf("原始数组: ");
    print_array(arr1, n, "原始");
    
    bubble_sort_traditional(arr1, n);
    print_array(arr1, n, "传统排序");
    
    bubble_sort_pointer(arr2, n);
    print_array(arr2, n, "指针排序");
    
    bubble_sort_optimized(arr3, n);
    print_array(arr3, n, "优化指针");
    
    return 0;
}

指针视角分析

  • 数组名arr作为指针参数传递

  • 指针运算arr + i替代数组索引arr[i]

  • 指针比较current < end作为循环条件

  • 通过指针直接操作内存,提升效率

12. 二级指针

指向指针的指针:

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

int main() 
{
    int value = 100;
    int *ptr = &value;
    int **pptr = &ptr;  // 二级指针
    
    printf("变量关系:\n");
    printf("value = %d, &value = %p\n", value, (void*)&value);
    printf("*ptr = %d, ptr = %p, &ptr = %p\n", *ptr, (void*)ptr, (void*)&ptr);
    printf("**pptr = %d, *pptr = %p, pptr = %p\n", **pptr, (void*)*pptr, (void*)pptr);
    
    // 通过二级指针修改变量值
    **pptr = 200;
    printf("\n修改后 value = %d\n", value);
    
    // 动态内存分配示例
    int **matrix = (int**)malloc(3 * sizeof(int*));
    for(int i = 0; i < 3; i++) 
    {
        matrix[i] = (int*)malloc(4 * sizeof(int));
    }
    
    // 使用矩阵...
    
    // 释放内存
    for(int i = 0; i < 3; i++) 
    {
        free(matrix[i]);
    }
    free(matrix);
    
    return 0;
}

13. 指针数组

元素为指针的数组:

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

int main() 
{
    // 整数指针数组
    int a = 10, b = 20, c = 30;
    int *ptr_arr[3] = {&a, &b, &c};
    
    printf("整数指针数组:\n");
    for(int i = 0; i < 3; i++) 
    {
        printf("ptr_arr[%d] = %p, *ptr_arr[%d] = %d\n", 
               i, (void*)ptr_arr[i], i, *ptr_arr[i]);
    }
    
    // 字符串指针数组
    char *names[] = {"Alice", "Bob", "Charlie", "David"};
    int name_count = sizeof(names) / sizeof(names[0]);
    
    printf("\n字符串指针数组:\n");
    for(int i = 0; i < name_count; i++) 
    {
        printf("names[%d] = %s, 地址: %p\n", i, names[i], (void*)names[i]);
    }
    
    return 0;
}

14. 指针数组模拟二维数组

用指针数组实现类似二维数组的功能:

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

int main() 
{
    int rows = 3, cols = 4;
    
    // 创建指针数组
    int **array = (int**)malloc(rows * sizeof(int*));
    
    // 为每一行分配内存
    for(int i = 0; i < rows; i++) 
    {
        array[i] = (int*)malloc(cols * sizeof(int));
    }
    
    // 初始化数组
    int counter = 1;
    for(int i = 0; i < rows; i++) 
    {
        for(int j = 0; j < cols; j++) 
        {
            array[i][j] = counter++;
        }
    }
    
    // 打印数组
    printf("模拟的二维数组:\n");
    for(int i = 0; i < rows; i++) 
    {
        for(int j = 0; j < cols; j++) 
        {
            printf("%2d ", array[i][j]);
        }
        printf("\n");
    }
    
    // 使用指针算术访问
    printf("\n使用指针算术访问:\n");
    for(int i = 0; i < rows; i++) 
    {
        int *row_ptr = array[i];
        for(int j = 0; j < cols; j++) 
        {
            printf("%2d ", *(row_ptr + j));
        }
        printf("\n");
    }
    
    // 释放内存
    for(int i = 0; i < rows; i++) 
    {
        free(array[i]);
    }
    free(array);
    
    return 0;
}

15. 字符指针变量

字符指针的特殊用法:

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

int main() 
{
    // 字符数组
    char str1[] = "Hello World";
    
    // 字符指针指向字符串常量
    char *str2 = "Hello World";
    
    printf("字符数组: %s\n", str1);
    printf("字符指针: %s\n", str2);
    
    // 重要区别
    str1[0] = 'h';  // 正确:修改栈上数组
    // str2[0] = 'h';  // 错误:修改只读内存
    
    str2 = "New String";  // 正确:改变指针指向
    
    printf("修改后:\n");
    printf("str1: %s\n", str1);
    printf("str2: %s\n", str2);
    
    // 字符指针遍历
    char *ptr = str1;
    printf("\n字符指针遍历: ");
    while(*ptr != '\0') 
    {
        printf("%c", *ptr);
        ptr++;
    }
    printf("\n");
    
    return 0;
}

16. 数组指针变量

指向整个数组的指针:

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

int main() 
{
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    
    // 数组指针 - 指向包含3个int的数组
    int (*ptr)[3] = arr;
    
    printf("数组指针演示:\n");
    for(int i = 0; i < 2; i++) 
    {
        for(int j = 0; j < 3; j++) 
        {
            printf("ptr[%d][%d] = %d, 地址: %p\n", 
                   i, j, ptr[i][j], (void*)&ptr[i][j]);
        }
    }
    
    // 指针运算
    printf("\n指针运算:\n");
    printf("ptr = %p\n", (void*)ptr);
    printf("ptr + 1 = %p (跳过%d字节)\n", 
           (void*)(ptr + 1), (int)((char*)(ptr + 1) - (char*)ptr));
    
    return 0;
}

17. 二维数组传参的本质

二维数组参数传递的真相:

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

// 方式1:指定第二维大小
void print_2d_array1(int arr[][3], int rows) 
{
    printf("方式1 - 指定列数:\n");
    for(int i = 0; i < rows; i++) 
    {
        for(int j = 0; j < 3; j++) 
        {
            printf("%d ", arr[i][j]);
        }
        printf("\n");
    }
}

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

// 方式3:作为一维数组处理
void print_2d_array3(int *arr, int rows, int cols) 
{
    printf("方式3 - 一维数组视角:\n");
    for(int i = 0; i < rows; i++) 
    {
        for(int j = 0; j < cols; j++) 
        {
            printf("%d ", *(arr + i * cols + j));
        }
        printf("\n");
    }
}

int main() 
{
    int arr[2][3] = {{1, 2, 3}, {4, 5, 6}};
    
    print_2d_array1(arr, 2);
    printf("\n");
    print_2d_array2(arr, 2);
    printf("\n");
    print_2d_array3(&arr[0][0], 2, 3);
    
    return 0;
}

18. 函数指针变量

指向函数的指针:

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

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

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

void greet() 
{
    printf("Hello from function pointer!\n");
}

int main() 
{
    // 函数指针声明
    int (*func_ptr)(int, int);
    void (*void_func_ptr)();
    
    // 指向add函数
    func_ptr = add;
    printf("加法: %d\n", func_ptr(10, 20));
    
    // 指向multiply函数
    func_ptr = multiply;
    printf("乘法: %d\n", func_ptr(10, 20));
    
    // 无参函数指针
    void_func_ptr = greet;
    void_func_ptr();
    
    return 0;
}

19. 函数指针数组

管理多个相关函数:

cpp 复制代码
#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 *operation_names[] = {"加法", "减法", "乘法", "除法"};
    
    int a = 100, b = 25;
    int operation_count = sizeof(operations) / sizeof(operations[0]);
    
    printf("函数指针数组演示:\n");
    for(int i = 0; i < operation_count; i++) 
    {
        int result = operations[i](a, b);
        printf("%s: %d %c %d = %d\n", 
               operation_names[i], a, 
               i == 0 ? '+' : i == 1 ? '-' : i == 2 ? '*' : '/', 
               b, result);
    }
    
    return 0;
}

20. 转移表

使用函数指针数组实现跳转表:

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

// 计算器操作函数
double add(double a, double b) 
{
    return a + b;
}
double subtract(double a, double b) 
{ 
    return a - b; 
}
double multiply(double a, double b) 
{ 
    return a * b; 
}
double divide(double a, double b) 
{ 
    if(b == 0) 
    {
        printf("错误: 除数不能为0\n");
        return 0;
    }
    return a / b; 
}

void print_menu() 
{
    printf("\n=== 简单计算器 ===\n");
    printf("1. 加法\n");
    printf("2. 减法\n");
    printf("3. 乘法\n");
    printf("4. 除法\n");
    printf("0. 退出\n");
    printf("请选择操作: ");
}

int main() 
{
    // 转移表 - 函数指针数组
    double (*calculator[])(double, double) = {NULL, add, subtract, multiply, divide};
    
    int choice;
    double num1, num2;
    
    do 
    {
        print_menu();
        scanf("%d", &choice);
        
        if(choice == 0) 
        {
            printf("再见!\n");
            break;
        }
        
        if(choice < 1 || choice > 4) 
        {
            printf("无效选择!\n");
            continue;
        }
        
        printf("输入两个数字: ");
        scanf("%lf %lf", &num1, &num2);
        
        // 使用转移表调用对应函数
        double result = calculator[choice](num1, num2);
        printf("结果: %.2lf\n", result);
        
    } while(1);
    
    return 0;
}

21. 回调函数

回调函数的原理和应用:

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

// 回调函数类型定义
typedef void (*CallbackFunc)(int, void*);

// 处理数据的函数,接受回调函数作为参数
void process_data(int *array, int size, CallbackFunc callback, void *user_data) 
{
    printf("开始处理数据...\n");
    
    for(int i = 0; i < size; i++) 
    {
        // 对每个元素调用回调函数
        callback(array[i], user_data);
    }
    
    printf("数据处理完成\n");
}

// 不同的回调函数实现

// 回调1: 打印元素
void print_element(int value, void *data) 
{
    printf("%d ", value);
}

// 回调2: 累加元素
void sum_elements(int value, void *data) 
{
    int *sum = (int*)data;
    *sum += value;
}

// 回调3: 找最大值
void find_max(int value, void *data) 
{
    int *max = (int*)data;
    if(value > *max) 
    {
        *max = value;
    }
}

int main() 
{
    int numbers[] = {23, 45, 12, 67, 89, 34};
    int size = sizeof(numbers) / sizeof(numbers[0]);
    
    printf("原始数组: ");
    process_data(numbers, size, print_element, NULL);
    printf("\n");
    
    int total = 0;
    process_data(numbers, size, sum_elements, &total);
    printf("数组总和: %d\n", total);
    
    int maximum = numbers[0];
    process_data(numbers, size, find_max, &maximum);
    printf("数组最大值: %d\n", maximum);
    
    return 0;
}

22. qsort使用举例

标准库qsort函数的使用:

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

// 整数比较函数
int compare_int(const void *a, const void *b) 
{
    return (*(int*)a - *(int*)b);
}

// 字符串比较函数
int compare_string(const void *a, const void *b) 
{
    return strcmp(*(const char**)a, *(const char**)b);
}

// 结构体比较函数
typedef struct 
{
    char name[50];
    int age;
    double salary;
} Person;

int compare_person_by_age(const void *a, const void *b) 
{
    return ((Person*)a)->age - ((Person*)b)->age;
}

int compare_person_by_name(const void *a, const void *b) 
{
    return strcmp(((Person*)a)->name, ((Person*)b)->name);
}

void print_array(int *arr, int n, const char *message) 
{
    printf("%s: ", message);
    for(int i = 0; i < n; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

void print_strings(char **strings, int n, const char *message) 
{
    printf("%s: ", message);
    for(int i = 0; i < n; i++) 
    {
        printf("%s ", strings[i]);
    }
    printf("\n");
}

void print_persons(Person *persons, int n, const char *message) 
{
    printf("%s:\n", message);
    for(int i = 0; i < n; i++) 
    {
        printf("  姓名: %s, 年龄: %d, 薪资: %.2lf\n", 
               persons[i].name, persons[i].age, persons[i].salary);
    }
}

int main() 
{
    // 1. 整数数组排序
    int numbers[] = {64, 34, 25, 12, 22, 11, 90};
    int num_count = sizeof(numbers) / sizeof(numbers[0]);
    
    print_array(numbers, num_count, "排序前");
    qsort(numbers, num_count, sizeof(int), compare_int);
    print_array(numbers, num_count, "排序后");
    
    printf("\n");
    
    // 2. 字符串数组排序
    char *names[] = {"Charlie", "Alice", "David", "Bob"};
    int name_count = sizeof(names) / sizeof(names[0]);
    
    print_strings(names, name_count, "排序前");
    qsort(names, name_count, sizeof(char*), compare_string);
    print_strings(names, name_count, "排序后");
    
    printf("\n");
    
    // 3. 结构体数组排序
    Person employees[] = 
    {
        {"张三", 25, 5000.0},
        {"李四", 30, 8000.0},
        {"王五", 22, 4000.0},
        {"赵六", 35, 10000.0}
    };
    int emp_count = sizeof(employees) / sizeof(employees[0]);
    
    print_persons(employees, emp_count, "按年龄排序前");
    qsort(employees, emp_count, sizeof(Person), compare_person_by_age);
    print_persons(employees, emp_count, "按年龄排序后");
    
    qsort(employees, emp_count, sizeof(Person), compare_person_by_name);
    print_persons(employees, emp_count, "按姓名排序后");
    
    return 0;
}

23. qsort函数的模拟实现

理解qsort的工作原理:

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

// 交换函数 - 使用字节操作
void swap(void *a, void *b, size_t size) 
{
    char temp[size];
    memcpy(temp, a, size);
    memcpy(a, b, size);
    memcpy(b, temp, size);
}

// 快速排序分区函数
int partition(void *base, int low, int high, size_t size, 
              int (*compar)(const void*, const void*)) 
{
    char *bytes = (char*)base;
    void *pivot = bytes + high * size;
    int i = low - 1;
    
    for(int j = low; j < high; j++) 
    {
        if(compar(bytes + j * size, pivot) <= 0) 
        {
            i++;
            swap(bytes + i * size, bytes + j * size, size);
        }
    }
    swap(bytes + (i + 1) * size, bytes + high * size, size);
    return i + 1;
}

// 模拟qsort函数
void my_qsort(void *base, int count, size_t size, 
              int (*compar)(const void*, const void*)) 
{
    if(count <= 1) return;
    
    // 使用栈模拟递归,避免栈溢出
    int stack[count];
    int top = -1;
    
    stack[++top] = 0;
    stack[++top] = count - 1;
    
    while(top >= 0) 
    {
        int high = stack[top--];
        int low = stack[top--];
        
        int pi = partition(base, low, high, size, compar);
        
        if(pi - 1 > low) 
        {
            stack[++top] = low;
            stack[++top] = pi - 1;
        }
        
        if(pi + 1 < high) 
        {
            stack[++top] = pi + 1;
            stack[++top] = high;
        }
    }
}

// 测试比较函数
int compare_int(const void *a, const void *b) 
{
    return (*(int*)a - *(int*)b);
}

void print_array(int *arr, int n, const char *message) 
{
    printf("%s: ", message);
    for(int i = 0; i < n; i++) 
    {
        printf("%d ", arr[i]);
    }
    printf("\n");
}

int main() 
{
    int numbers[] = {64, 34, 25, 12, 22, 11, 90, 5, 77, 43};
    int count = sizeof(numbers) / sizeof(numbers[0]);
    
    print_array(numbers, count, "排序前");
    my_qsort(numbers, count, sizeof(int), compare_int);
    print_array(numbers, count, "排序后");
    
    return 0;
}

24. sizeof和strlen的对比

理解两个关键操作符的区别:

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

int main() 
{
    // 情况1: 字符数组
    char str1[] = "Hello";
    printf("char str1[] = \"Hello\";\n");
    printf("sizeof(str1) = %zu (包含空字符)\n", sizeof(str1));
    printf("strlen(str1) = %zu (字符串长度)\n\n", strlen(str1));
    
    // 情况2: 字符指针
    char *str2 = "Hello";
    printf("char *str2 = \"Hello\";\n");
    printf("sizeof(str2) = %zu (指针大小)\n", sizeof(str2));
    printf("strlen(str2) = %zu (字符串长度)\n\n", strlen(str2));
    
    // 情况3: 数组作为参数
    char str3[100] = "Hello";
    printf("char str3[100] = \"Hello\";\n");
    printf("sizeof(str3) = %zu (数组总大小)\n", sizeof(str3));
    printf("strlen(str3) = %zu (字符串长度)\n\n", strlen(str3));
    
    // 情况4: 数组名和指针的区别
    printf("数组名和指针的区别:\n");
    printf("sizeof(str1) = %zu (数组大小)\n", sizeof(str1));
    printf("sizeof(&str1[0]) = %zu (指针大小)\n", sizeof(&str1[0]));
    
    return 0;
}

25. 数组和指针笔试题解析

经典面试题分析:

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

void array_pointer_test() 
{
    printf("=== 数组和指针笔试题 ===\n\n");
    
    int a[] = {1, 2, 3, 4};
    
    printf("int a[] = {1, 2, 3, 4};\n");
    printf("sizeof(a) = %zu\n", sizeof(a));        // 整个数组大小
    printf("sizeof(a + 0) = %zu\n", sizeof(a + 0)); // 指针大小
    printf("sizeof(*a) = %zu\n", sizeof(*a));      // int大小
    printf("sizeof(a + 1) = %zu\n", sizeof(a + 1)); // 指针大小
    printf("sizeof(a[1]) = %zu\n", sizeof(a[1]));  // int大小
    printf("sizeof(&a) = %zu\n", sizeof(&a));      // 指针大小
    printf("sizeof(*&a) = %zu\n", sizeof(*&a));    // 整个数组大小
    printf("sizeof(&a + 1) = %zu\n", sizeof(&a + 1)); // 指针大小
    printf("sizeof(&a[0]) = %zu\n", sizeof(&a[0])); // 指针大小
    printf("sizeof(&a[0] + 1) = %zu\n\n", sizeof(&a[0] + 1)); // 指针大小
    
    // 地址分析
    printf("地址分析:\n");
    printf("a = %p\n", (void*)a);
    printf("&a[0] = %p\n", (void*)&a[0]);
    printf("&a = %p\n", (void*)&a);
    printf("a + 1 = %p\n", (void*)(a + 1));
    printf("&a[0] + 1 = %p\n", (void*)(&a[0] + 1));
    printf("&a + 1 = %p\n", (void*)(&a + 1));
}

void string_pointer_test() 
{
    printf("\n=== 字符串指针测试 ===\n\n");
    
    char str[] = "hello";
    char *p = str;
    
    printf("char str[] = \"hello\";\n");
    printf("char *p = str;\n\n");
    
    printf("sizeof(str) = %zu\n", sizeof(str));
    printf("sizeof(p) = %zu\n", sizeof(p));
    printf("sizeof(*p) = %zu\n", sizeof(*p));
    
    printf("\n指针运算:\n");
    printf("p = %p, *p = '%c'\n", (void*)p, *p);
    printf("p + 1 = %p, *(p + 1) = '%c'\n", (void*)(p + 1), *(p + 1));
    printf("&p[2] = %p, p[2] = '%c'\n", (void*)&p[2], p[2]);
}

int main() 
{
    array_pointer_test();
    string_pointer_test();
    return 0;
}

26. 指针运算笔试题解析

深入理解指针运算:

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

void pointer_arithmetic_test() 
{
    printf("=== 指针运算笔试题 ===\n\n");
    
    int arr[5] = {1, 2, 3, 4, 5};
    int *ptr = arr;
    
    printf("int arr[5] = {1, 2, 3, 4, 5};\n");
    printf("int *ptr = arr;\n\n");
    
    // 基础指针运算
    printf("基础运算:\n");
    printf("*ptr = %d\n", *ptr);
    printf("*(ptr + 2) = %d\n", *(ptr + 2));
    printf("ptr[3] = %d\n", ptr[3]);
    
    // 复杂表达式
    printf("\n复杂表达式:\n");
    printf("*ptr++ = %d\n", *ptr++);  // 先取值,后递增
    printf("现在 *ptr = %d\n", *ptr);
    
    printf("*++ptr = %d\n", *++ptr);  // 先递增,后取值
    printf("现在 *ptr = %d\n", *ptr);
    
    printf("(*ptr)++ = %d\n", (*ptr)++); // 先取值,后值递增
    printf("现在 *ptr = %d\n", *ptr);
    printf("现在 arr[2] = %d\n", arr[2]);
    
    // 重置指针
    ptr = arr;
    printf("\n指针比较:\n");
    printf("ptr < arr + 4: %d\n", ptr < arr + 4);
    printf("ptr == arr: %d\n", ptr == arr);
}

void multi_dimensional_test() 
{
    printf("\n=== 多维数组指针运算 ===\n\n");
    
    int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};
    
    printf("int matrix[2][3] = {{1, 2, 3}, {4, 5, 6}};\n\n");
    
    printf("matrix = %p\n", (void*)matrix);
    printf("matrix[0] = %p\n", (void*)matrix[0]);
    printf("&matrix[0][0] = %p\n", (void*)&matrix[0][0]);
    
    printf("\n指针类型分析:\n");
    printf("sizeof(matrix) = %zu\n", sizeof(matrix));        // 整个数组
    printf("sizeof(matrix[0]) = %zu\n", sizeof(matrix[0]));  // 一行
    printf("sizeof(matrix[0][0]) = %zu\n", sizeof(matrix[0][0])); // 一个元素
    
    printf("\n地址运算:\n");
    printf("matrix + 1 = %p (跳过一行)\n", (void*)(matrix + 1));
    printf("matrix[0] + 1 = %p (跳过一个元素)\n", (void*)(matrix[0] + 1));
}

int main() 
{
    pointer_arithmetic_test();
    multi_dimensional_test();
    return 0;
}

总结

通过本文的全面解析,我们可以看到指针在C语言中的核心地位和广泛应用。从基础的内存地址概念,到复杂的函数指针和回调机制,指针贯穿了C语言的各个方面。

关键要点总结

  1. 指针本质:指针是内存地址的变量化表示,提供了直接操作内存的能力

  2. 类型安全:指针类型确保了内存访问的正确性和安全性

  3. 运算灵活性:指针运算使得高效的数据处理成为可能

  4. 函数抽象:函数指针实现了运行时多态和回调机制

  5. 内存管理:正确使用指针是有效内存管理的基础

即使是看似与指针无关的算法如冒泡排序,在深入分析后也揭示了与指针的紧密联系。理解指针不仅有助于编写高效的C代码,更是深入理解计算机系统工作原理的重要途径。

指针的学习需要理论与实践相结合,通过不断的练习和思考,才能真正掌握这一强大而灵活的工具。

相关推荐
W.D.小糊涂2 小时前
Qt 5.14.2+Mysql5.7 64位开发环境下无法连接数据库
开发语言·qt
_OP_CHEN3 小时前
C++基础:(八)STL简介
开发语言·c++·面试·stl
无敌最俊朗@3 小时前
Qt 多线程与并发编程详解
linux·开发语言·qt
消失的旧时光-19433 小时前
Kotlin Flow 与“天然背压”(完整示例)
android·开发语言·kotlin
ClassOps3 小时前
Kotlin invoke 函数调用重载
android·开发语言·kotlin
wdfk_prog4 小时前
[Linux]学习笔记系列 -- lib/timerqueue.c Timer Queue Management 高精度定时器的有序数据结构
linux·c语言·数据结构·笔记·单片机·学习·安全
小苏兮4 小时前
【C++】stack与queue的使用与模拟实现
开发语言·c++
高山有多高5 小时前
栈:“后进先出” 的艺术,撑起程序世界的底层骨架
c语言·开发语言·数据结构·c++·算法
蔗理苦5 小时前
2025-10-07 Python不基础 19——私有对象
开发语言·python·私有对象