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已成为悬空指针
最佳实践:
- 总是检查malloc的返回值是否为NULL
- 释放内存后立即将指针设为NULL
- 避免内存泄漏和悬空指针
- 使用valgrind等工具检测内存问题
9. 总结
在本篇教程中,你学到了指针的高级应用:
- 指针与数组的密切关系和相互转换
- 指针作为函数参数实现按引用传递
- 动态内存分配(malloc/free)的强大功能
- 指针数组和数组指针的区别与使用
- 多级指针的层次结构
- 函数指针的回调机制
- 复杂的动态字符串数组管理
10. 动手练习
- 基础练习:使用指针实现字符串的复制、连接和比较函数。
- 进阶练习:使用动态内存分配创建可变大小的矩阵,实现矩阵加减法。
- 挑战练习:实现一个简单的联系人管理系统,使用动态内存管理联系人的增删改查。