C语言指针高级教程,指针与数组关系、函数参数传递、动态内存分配详解

​C语言指针进阶,指针与数组、函数参数传递、动态内存分配关系终极学习教程​。​

当你掌握了C语言中指针的基本概念后,我们现在要探索指针真正强大的应用场景。指针与数组、函数的结合,以及动态内存管理,是C语言编程的精髓所在。

本篇教程将带你深入这些高级主题,让你真正领略指针的魅力。

1. 指针与数组的密切关系

数组名本质上是一个指向数组首元素的常量指针。

#include <stdio.h>

int main() {
    int numbers[] = {10, 20, 30, 40, 50};
    
    // 数组名是指向第一个元素的指针
    printf("数组名: %p\n", numbers);
    printf("第一个元素的地址: %p\n", &numbers[0]);
    printf("两者是否相等? %s\n", numbers == &numbers[0] ? "是" : "否");
    
    // 通过指针访问数组元素
    printf("\n通过指针访问数组:\n");
    for (int i = 0; i < 5; i++) {
        printf("numbers[%d] = %d\n", i, *(numbers + i));
    }
    
    // 数组名与指针的区别
    int *ptr = numbers;
    printf("\n指针运算:\n");
    printf("ptr = %p, *ptr = %d\n", ptr, *ptr);
    
    ptr++;  // 指针可以递增
    printf("ptr++后: ptr = %p, *ptr = %d\n", ptr, *ptr);
    
    // numbers++;  // 错误!数组名是常量指针,不能修改
    
    return 0;
}

2. 指针作为函数参数(模拟按引用传递)

这是指针最重要的应用之一,允许函数修改调用者的变量。

#include <stdio.h>

// 修改基本类型变量
void increment(int *num) {
    (*num)++;  // 修改指针指向的值
}

// 修改数组元素
void modifyArray(int *arr, int size) {
    for (int i = 0; i < size; i++) {
        arr[i] *= 2;  // 等价于 *(arr + i) *= 2
    }
}

// 返回多个值(通过指针参数)
void calculate(int a, int b, int *sum, int *product) {
    *sum = a + b;
    *product = a * b;
}

// 交换两个变量的值
void swap(int *a, int *b) {
    int temp = *a;
    *a = *b;
    *b = temp;
}

int main() {
    // 1. 修改基本变量
    int count = 5;
    printf("原始值: %d\n", count);
    increment(&count);
    printf("递增后: %d\n", count);
    
    // 2. 修改数组
    int numbers[] = {1, 2, 3, 4, 5};
    printf("\n原始数组: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]);
    }
    
    modifyArray(numbers, 5);
    printf("\n修改后数组: ");
    for (int i = 0; i < 5; i++) {
        printf("%d ", numbers[i]);
    }
    
    // 3. 返回多个值
    int x = 10, y = 20;
    int result_sum, result_product;
    calculate(x, y, &result_sum, &result_product);
    printf("\n\n%d + %d = %d\n", x, y, result_sum);
    printf("%d × %d = %d\n", x, y, result_product);
    
    // 4. 交换变量
    int a = 100, b = 200;
    printf("\n交换前: a=%d, b=%d\n", a, b);
    swap(&a, &b);
    printf("交换后: a=%d, b=%d\n", a, b);
    
    return 0;
}

3. 动态内存分配:malloc和free

动态内存分配允许程序在运行时请求内存,这是指针最强大的功能之一。

#include <stdio.h>
#include <stdlib.h>  // 包含malloc和free

int main() {
    int *dynamicArray;
    int size;
    
    printf("请输入数组大小: ");
    scanf("%d", &size);
    
    // 动态分配内存
    dynamicArray = (int*)malloc(size * sizeof(int));
    
    if (dynamicArray == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 使用动态数组
    printf("请输入%d个整数:\n", size);
    for (int i = 0; i < size; i++) {
        printf("元素 %d: ", i);
        scanf("%d", &dynamicArray[i]);
    }
    
    printf("\n您输入的数组: ");
    for (int i = 0; i < size; i++) {
        printf("%d ", dynamicArray[i]);
    }
    
    // 计算平均值
    int sum = 0;
    for (int i = 0; i < size; i++) {
        sum += dynamicArray[i];
    }
    printf("\n平均值: %.2f\n", (float)sum / size);
    
    // 必须释放分配的内存!
    free(dynamicArray);
    dynamicArray = NULL;  // 避免野指针
    
    printf("内存已释放\n");
    
    return 0;
}

4. 指针数组和数组指针

这是两个容易混淆但很重要的概念。

#include <stdio.h>

int main() {
    int a = 10, b = 20, c = 30;
    
    // 1. 指针数组:数组的每个元素都是指针
    int *pointerArray[3] = {&a, &b, &c};
    
    printf("指针数组:\n");
    for (int i = 0; i < 3; i++) {
        printf("pointerArray[%d] = %p, *pointerArray[%d] = %d\n", 
               i, pointerArray[i], i, *pointerArray[i]);
    }
    
    // 2. 数组指针:指向整个数组的指针
    int numbers[3] = {100, 200, 300};
    int (*arrayPointer)[3] = &numbers;  // 指向整个数组的指针
    
    printf("\n数组指针:\n");
    printf("数组地址: %p\n", arrayPointer);
    printf("第一个元素: %d\n", (*arrayPointer)[0]);
    printf("第二个元素: %d\n", (*arrayPointer)[1]);
    
    // 3. 指针数组的实用示例:字符串数组
    char *names[] = {"Alice", "Bob", "Charlie", "Diana"};
    int nameCount = sizeof(names) / sizeof(names[0]);
    
    printf("\n字符串数组:\n");
    for (int i = 0; i < nameCount; i++) {
        printf("名字 %d: %s\n", i + 1, names[i]);
    }
    
    return 0;
}

5. 多级指针(指针的指针)

指针也可以指向其他指针,形成多级指针。

#include <stdio.h>

int main() {
    int value = 42;
    int *singlePtr = &value;     // 一级指针
    int **doublePtr = &singlePtr; // 二级指针
    int ***triplePtr = &doublePtr; // 三级指针
    
    printf("变量的值: %d\n", value);
    printf("变量的地址: %p\n", &value);
    
    printf("\n一级指针:\n");
    printf("singlePtr = %p\n", singlePtr);
    printf("*singlePtr = %d\n", *singlePtr);
    
    printf("\n二级指针:\n");
    printf("doublePtr = %p\n", doublePtr);
    printf("*doublePtr = %p (singlePtr的地址)\n", *doublePtr);
    printf("**doublePtr = %d\n", **doublePtr);
    
    printf("\n三级指针:\n");
    printf("triplePtr = %p\n", triplePtr);
    printf("*triplePtr = %p\n", *triplePtr);
    printf("**triplePtr = %p\n", **triplePtr);
    printf("***triplePtr = %d\n", ***triplePtr);
    
    // 通过多级指针修改变量值
    ***triplePtr = 100;
    printf("\n修改后的值: %d\n", value);
    
    return 0;
}

6. 函数指针:指向函数的指针

函数指针可以指向函数,实现回调等高级功能。

#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) {
    if (b != 0) return a / b;
    return 0;
}

// 使用函数指针作为参数(回调函数)
void calculate(int a, int b, int (*operation)(int, int)) {
    int result = operation(a, b);
    printf("计算结果: %d\n", result);
}

int main() {
    int x = 20, y = 5;
    
    // 声明函数指针
    int (*funcPtr)(int, int);
    
    printf("函数指针演示:\n");
    
    // 指向add函数
    funcPtr = add;
    printf("%d + %d = ", x, y);
    calculate(x, y, funcPtr);
    
    // 指向subtract函数
    funcPtr = subtract;
    printf("%d - %d = ", x, y);
    calculate(x, y, funcPtr);
    
    // 直接使用函数名
    printf("%d × %d = ", x, y);
    calculate(x, y, multiply);
    
    printf("%d ÷ %d = ", x, y);
    calculate(x, y, divide);
    
    // 函数指针数组
    int (*operations[4])(int, int) = {add, subtract, multiply, divide};
    char *operationNames[] = {"加法", "减法", "乘法", "除法"};
    
    printf("\n函数指针数组:\n");
    for (int i = 0; i < 4; i++) {
        printf("%s结果: %d\n", operationNames[i], operations[i](x, y));
    }
    
    return 0;
}

7. 实战案例:动态字符串数组管理

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

int main() {
    char **stringArray;  // 指向指针的指针(字符串数组)
    int stringCount;
    
    printf("请输入字符串数量: ");
    scanf("%d", &stringCount);
    getchar();  // 消耗换行符
    
    // 分配字符串指针数组
    stringArray = (char**)malloc(stringCount * sizeof(char*));
    
    if (stringArray == NULL) {
        printf("内存分配失败!\n");
        return 1;
    }
    
    // 为每个字符串分配内存
    for (int i = 0; i < stringCount; i++) {
        char buffer[100];
        printf("请输入字符串 %d: ", i + 1);
        fgets(buffer, sizeof(buffer), stdin);
        
        // 去除换行符
        buffer[strcspn(buffer, "\n")] = '\0';
        
        // 分配内存并复制字符串
        stringArray[i] = (char*)malloc(strlen(buffer) + 1);
        strcpy(stringArray[i], buffer);
    }
    
    // 显示所有字符串
    printf("\n您输入的字符串:\n");
    for (int i = 0; i < stringCount; i++) {
        printf("%d: %s (长度: %zu)\n", i + 1, stringArray[i], strlen(stringArray[i]));
    }
    
    // 按长度排序字符串(冒泡排序)
    for (int i = 0; i < stringCount - 1; i++) {
        for (int j = 0; j < stringCount - i - 1; j++) {
            if (strlen(stringArray[j]) > strlen(stringArray[j + 1])) {
                // 交换指针(不交换字符串内容)
                char *temp = stringArray[j];
                stringArray[j] = stringArray[j + 1];
                stringArray[j + 1] = temp;
            }
        }
    }
    
    printf("\n按长度排序后:\n");
    for (int i = 0; i < stringCount; i++) {
        printf("%d: %s\n", i + 1, stringArray[i]);
    }
    
    // 释放内存
    for (int i = 0; i < stringCount; i++) {
        free(stringArray[i]);
    }
    free(stringArray);
    
    printf("\n内存已释放\n");
    
    return 0;
}

8. 常见错误与最佳实践

常见错误1:内存泄漏

// 错误:分配内存后忘记释放
int *ptr = (int*)malloc(100 * sizeof(int));
// ... 使用ptr
// 忘记写 free(ptr);

常见错误2:悬空指针

int *ptr = (int*)malloc(sizeof(int));
free(ptr);
*ptr = 10;  // 错误!ptr已成为悬空指针

最佳实践:​

  1. 总是检查malloc的返回值是否为NULL
  2. 释放内存后立即将指针设为NULL
  3. 避免内存泄漏和悬空指针
  4. 使用valgrind等工具检测内存问题

9. 总结

在本篇教程中,你学到了指针的高级应用:

  • 指针与数组的密切关系和相互转换
  • 指针作为函数参数实现按引用传递
  • 动态内存分配​(malloc/free)的强大功能
  • 指针数组数组指针的区别与使用
  • 多级指针的层次结构
  • 函数指针的回调机制
  • 复杂的动态字符串数组管理

10. 动手练习

  1. 基础练习​:使用指针实现字符串的复制、连接和比较函数。
  2. 进阶练习​:使用动态内存分配创建可变大小的矩阵,实现矩阵加减法。
  3. 挑战练习​:实现一个简单的联系人管理系统,使用动态内存管理联系人的增删改查。

发表评论