一 函数指针
函数指针是一种指向函数的指针,允许动态调用不同的函数,增强代码的灵活性。以下是其具体用法及示例:
1. 声明函数指针
// 函数原型:int add(int a, int b);
int (*func_ptr)(int, int); // 声明函数指针,参数为两个int,返回int
2. 赋值与调用
func_ptr = add; // 或 func_ptr = &add;
int result = func_ptr(3, 4); // 调用方式1
int result = (*func_ptr)(3, 4); // 调用方式2(较少用)
3. 作为回调函数(示例:qsort)
#include <stdlib.h>
int compare(const void* a, const void* b) {
return (*(int*)a - *(int*)b);
}
int main() {
int arr[] = {5, 2, 8, 1};
qsort(arr, 4, sizeof(int), compare); // 传递函数指针
}
4. 函数指针数组
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int (*funcs[])(int, int) = {add, sub}; // 数组存储多个函数指针
int result = funcs[0](5, 3); // 调用add
5. 结构体中的函数指针(模拟面向对象)
typedef struct {
int (*operation)(int, int);
} Calculator;
Calculator calc;
calc.operation = add;
calc.operation(3, 4); // 调用add
6. 使用typedef简化声明
typedef int (*Operation)(int, int);
Operation op = add; // 简化后的声明
7. 函数指针作为返回值
Operation get_operation(char op) {
if (op == '+') return add;
else return sub;
}
8. 动态加载库函数(如dlsym)
#include <dlfcn.h>
void* handle = dlopen("lib.so", RTLD_LAZY);
int (*func)(int) = (int (*)(int))dlsym(handle, "func_name");
func(10); // 调用动态加载的函数
9. 注意事项
-
类型严格匹配:函数指针类型必须与目标函数完全一致(参数、返回类型)。
-
避免空指针调用:确保函数指针已初始化。
-
语法陷阱:
int *p(int, int);是返回int*的函数,而非函数指针。正确写法为int (*p)(int, int);。
应用场景
-
回调机制:如事件处理、排序算法。
-
策略模式:运行时切换不同算法。
-
插件系统:动态加载功能模块。
二 函数指针数组
函数指针数组是一种存储多个函数指针的数组,允许通过索引动态调用不同的函数,常用于实现状态机、命令表、菜单系统等场景。以下是其具体用法及示例:
1. 基本语法
(1) 声明函数指针数组
// 假设所有函数原型为:int func(int, int);
int (*func_array[5])(int, int); // 声明一个包含5个函数指针的数组
(2) 使用typedef简化
typedef int (*FuncPtr)(int, int); // 定义函数指针类型
FuncPtr func_array[5]; // 声明函数指针数组
2. 初始化与调用
(1) 静态初始化
int add(int a, int b) { return a + b; }
int sub(int a, int b) { return a - b; }
int mul(int a, int b) { return a * b; }
// 初始化数组
FuncPtr func_array[] = {add, sub, mul, NULL}; // 可以留空或置NULL
(2) 动态调用
int a = 10, b = 5;
for (int i = 0; i < 3; i++) {
int result = func_array[i](a, b); // 通过索引调用不同函数
printf("Result %d: %d\n", i, result);
}
// 输出:
// Result 0: 15 (add)
// Result 1: 5 (sub)
// Result 2: 50 (mul)
3. 典型应用场景
(1) 菜单系统(命令表)
#include <stdio.h>
typedef void (*MenuHandler)(); // 定义菜单处理函数类型
void NewFile() { printf("新建文件\n"); }
void OpenFile() { printf("打开文件\n"); }
void SaveFile() { printf("保存文件\n"); }
int main() {
MenuHandler menu[] = {NewFile, OpenFile, SaveFile};
int choice;
printf("菜单选项:\n0. 新建\n1. 打开\n2. 保存\n");
scanf("%d", &choice);
if (choice >= 0 && choice < 3) {
menu[choice](); // 根据输入调用对应函数
} else {
printf("无效选项\n");
}
return 0;
}
(2) 状态机(State Machine)
typedef void (*StateHandler)(); // 状态处理函数类型
void State_Idle() { printf("空闲状态\n"); }
void State_Working() { printf("工作状态\n"); }
void State_Error() { printf("错误状态\n"); }
int main() {
StateHandler states[] = {State_Idle, State_Working, State_Error};
int current_state = 0;
// 模拟状态切换
for (int i = 0; i < 3; i++) {
states[current_state]();
current_state = (current_state + 1) % 3;
}
return 0;
}
4. 高级用法
(1) 结合结构体实现扩展
typedef struct {
const char* name; // 函数名称
void (*func)(); // 函数指针
} Command;
void Start() { printf("启动\n"); }
void Stop() { printf("停止\n"); }
int main() {
Command commands[] = {
{"start", Start},
{"stop", Stop},
{NULL, NULL} // 结束标记
};
// 遍历执行所有命令
for (int i = 0; commands[i].name != NULL; i++) {
printf("执行命令: %s\n", commands[i].name);
commands[i].func();
}
return 0;
}
(2) 动态注册函数
FuncPtr func_array[10];
int count = 0;
// 动态注册函数到数组
void RegisterFunction(FuncPtr func) {
if (count < 10) {
func_array[count++] = func;
}
}
int main() {
RegisterFunction(add);
RegisterFunction(sub);
RegisterFunction(mul);
for (int i = 0; i < count; i++) {
printf("Result: %d\n", func_array[i](10, 5));
}
return 0;
}
5. 注意事项
-
类型一致性:数组中的所有函数指针必须具有相同的返回类型和参数列表。
-
越界检查:调用前需确保索引在有效范围内。
-
空指针处理:初始化时可为未使用的元素赋
NULL,调用前检查指针有效性if (func_array[i] != NULL) { func_array[i](a, b); }总结
函数指针数组的核心价值在于通过索引动态选择逻辑,适用于需要灵活切换行为或批量处理函数的场景。结合结构体或动态注册机制,可以进一步实现插件系统、命令分发等复杂功能。
三 指针数组
指针数组是一种特殊的数组,其元素均为指针类型,可以指向不同数据类型的内存地址。它在动态内存管理、多维数组模拟、字符串处理等场景中非常实用。以下是详细解析:
1. 指针数组 vs. 数组指针
合理使用指针数组能显著提升代码的灵活性和效率,但需谨慎处理内存和类型安全问题。
-
指针数组:数组元素均为指针。
int *arr[5]; // 包含5个int*类型元素的数组数组指针:指针指向一个数组。
int (*arr)[5]; // 指向包含5个int元素的数组的指针2. 常见类型及应用场景
(1) 整型指针数组
-
用途:动态创建二维数组、管理多个整型数据块。
-
示例:
#include <stdio.h> #include <stdlib.h> int main() { int rows = 3; int *arr[rows]; // 整型指针数组 // 为每个指针分配内存(模拟二维数组) for (int i = 0; i < rows; i++) { arr[i] = (int*)malloc(4 * sizeof(int)); // 每行4个元素 for (int j = 0; j < 4; j++) { arr[i][j] = i * 4 + j; // 赋值 } } // 访问数据 printf("arr[1][2] = %d\n", arr[1][2]); // 输出:6 // 释放内存 for (int i = 0; i < rows; i++) { free(arr[i]); } return 0; }(2) 字符指针数组
-
用途:存储多个字符串(如命令行参数)、高效处理字符串集合。
-
示例:
#include <stdio.h> int main() { // 字符指针数组(存储多个字符串) char *names[] = {"Alice", "Bob", "Charlie", NULL}; // 以NULL结尾 // 遍历打印 for (int i = 0; names[i] != NULL; i++) { printf("Name %d: %s\n", i, names[i]); } return 0; }输出:
Name 0: Alice Name 1: Bob Name 2: Charlie(3) 结构体指针数组
-
用途:高效管理大量结构体对象(如学生信息、游戏实体)。
-
示例:
#include <stdio.h> #include <stdlib.h> typedef struct { int id; char name[20]; } Student; int main() { Student *students[3]; // 结构体指针数组 // 动态分配并初始化 for (int i = 0; i < 3; i++) { students[i] = (Student*)malloc(sizeof(Student)); students[i]->id = i + 1; sprintf(students[i]->name, "Student%d", i + 1); } // 打印信息 for (int i = 0; i < 3; i++) { printf("ID: %d, Name: %s\n", students[i]->id, students[i]->name); } // 释放内存 for (int i = 0; i < 3; i++) { free(students[i]); } return 0; }(4) 函数指针数组
-
用途:实现回调机制、状态机、命令分发(详见之前讨论)。
-
简例:
#include <stdio.h> void func1() { printf("Function 1\n"); } void func2() { printf("Function 2\n"); } int main() { void (*funcs[])() = {func1, func2}; // 函数指针数组 funcs[0](); // 输出:Function 1 funcs[1](); // 输出:Function 2 return 0; }3. 指针数组的高级应用
(1) 动态命令行参数解析
int main(int argc, char *argv[]) { // argv 是字符指针数组,存储命令行参数 for (int i = 0; i < argc; i++) { printf("Arg %d: %s\n", i, argv[i]); } return 0; }(2) 多级指针与指针数组
-
示例:使用指针数组构建稀疏矩阵
int *matrix[10]; // 每行是一个int指针 for (int i = 0; i < 10; i++) { matrix[i] = (int*)calloc(10, sizeof(int)); // 初始化为0 } matrix[2][3] = 5; // 设置特定位置的值(3) 结合回调机制
typedef void (*Callback)(int); Callback callbacks[5]; // 存储回调函数 void RegisterCallback(int index, Callback func) { if (index >= 0 && index < 5) { callbacks[index] = func; } } void TriggerCallback(int index, int data) { if (callbacks[index] != NULL) { callbacks[index](data); } }4. 注意事项与常见错误
(1) 内存管理
-
内存泄漏:动态分配的指针数组需逐元素释放。
int *arr[5]; for (int i = 0; i < 5; i++) { arr[i] = malloc(sizeof(int) * 10); } // 释放内存 for (int i = 0; i < 5; i++) { free(arr[i]); }(2) 空指针与越界
-
空指针解引用:访问未初始化的指针。
int *arr[3]; printf("%d", *arr[0]); // 错误!arr[0]未初始化越界访问:索引超出数组范围。
int *arr[3]; arr[3] = NULL; // 错误!有效索引为0-2(3) 类型匹配
-
类型不匹配:指针类型与数据不一致。
float data = 3.14; int *arr[1]; arr[0] = &data; // 错误!int* 指向float5. 总结
-
核心价值:通过指针数组,可以高效管理多个同类型或不同类型的资源(内存、字符串、函数等)。
-
适用场景:
-
动态多维数组的实现。
-
字符串集合处理(如命令行参数)。
-
状态机、插件系统、回调机制。
-
结构体或对象的批量管理。
-
-
替代方案:若需动态调整大小,可考虑使用链表或动态数组(如C++的
std::vector)。
四 命令行参数
在C语言中,命令行参数是通过 main 函数的参数传递的,允许程序在启动时接收外部输入。这是实现命令行工具(如ls、gcc等)参数传递的核心机制。以下是详细的解析和示例:
1. 基本语法
main 函数的标准声明方式为:
int main(int argc, char *argv[]);
-
argc(Argument Count):参数数量,表示命令行参数的个数(包括程序自身名称)。 -
argv(Argument Vector):字符指针数组,存储所有命令行参数的字符串(以空格分隔)
2. 参数传递规则
-
程序名称是第一个参数:
argv[0]始终是程序自身的名称。 -
参数从索引1开始:用户输入的参数从
argv[1]开始存储。 -
参数以字符串形式存储:所有参数(包括数字)都会被转换为字符串。
示例
若执行以下命令
./my_program hello 42 -v
则参数解析为:
-
argc = 4 -
argv[0] = "./my_program"(程序名称) -
argv[1] = "hello" -
argv[2] = "42" -
argv[3] = "-v" -
argv[4] = NULL(数组末尾的哨兵值)
3. 基础示例
#include <stdio.h>
int main(int argc, char *argv[]) {
printf("程序名称: %s\n", argv[0]);
printf("参数总数: %d\n", argc - 1); // 减去程序名
for (int i = 1; i < argc; i++) {
printf("参数 %d: %s\n", i, argv[i]);
}
return 0;
}
输出(假设执行 ./a.out file.txt -o output):
程序名称: ./a.out
参数总数: 3
参数 1: file.txt
参数 2: -o
参数 3: output
4. 实际应用场景
(1) 文件操作参数
#include <stdio.h>
int main(int argc, char *argv[]) {
if (argc < 2) {
printf("用法: %s <文件名>\n", argv[0]);
return 1;
}
FILE *file = fopen(argv[1], "r");
if (!file) {
printf("无法打开文件: %s\n", argv[1]);
return 1;
}
// 读取文件内容...
fclose(file);
return 0;
}
(2) 数值参数处理
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv[]) {
if (argc != 3) {
printf("用法: %s <数字1> <数字2>\n", argv[0]);
return 1;
}
int a = atoi(argv[1]); // 字符串转整数
int b = atoi(argv[2]);
printf("%d + %d = %d\n", a, b, a + b);
return 0;
}
(3) 选项解析(如 -v 或 --help)
#include <stdio.h>
#include <string.h>
int main(int argc, char *argv[]) {
int verbose = 0;
for (int i = 1; i < argc; i++) {
if (strcmp(argv[i], "-v") == 0) {
verbose = 1;
} else if (strcmp(argv[i], "--help") == 0) {
printf("帮助信息: 这是一个示例程序\n");
return 0;
}
}
if (verbose) {
printf("详细模式已启用\n");
}
return 0;
}
5. 高级用法
(1) 使用 getopt 库解析复杂参数
getopt 是标准库中用于解析带选项(如 -f value)的命令行参数的工具。
#include <stdio.h>
#include <unistd.h> // 包含 getopt
int main(int argc, char *argv[]) {
int opt;
char *filename = NULL;
int verbose = 0;
while ((opt = getopt(argc, argv, "f:v")) != -1) {
switch (opt) {
case 'f':
filename = optarg; // optarg 是选项参数值
break;
case 'v':
verbose = 1;
break;
default:
fprintf(stderr, "用法: %s -f <文件名> [-v]\n", argv[0]);
return 1;
}
}
if (filename) {
printf("文件: %s\n", filename);
if (verbose) printf("详细模式已启用\n");
} else {
printf("必须指定文件名!\n");
}
return 0;
}
执行示例:
./a.out -f data.txt -v
(2) 参数类型验证
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
int is_number(const char *str) {
for (int i = 0; str[i]; i++) {
if (!isdigit(str[i])) return 0;
}
return 1;
}
int main(int argc, char *argv[]) {
if (argc != 2 || !is_number(argv[1])) {
printf("错误: 必须提供一个数字参数\n");
return 1;
}
int num = atoi(argv[1]);
printf("输入数字: %d\n", num);
return 0;
}
6. 注意事项
-
参数索引范围:
-
始终检查
argc的值,避免访问argv[argc](越界)。
-
-
字符串转换:
-
使用
atoi或strtol将字符串转为数字时,需验证输入合法性。
-
-
内存安全:
-
argv由系统管理,无需手动释放内存。
-
-
跨平台差异:
-
Windows 和 Linux 对命令行参数的分隔符处理可能不同(如含空格的路径需用引号包裹)。
-
7. 典型错误
// 错误1:未检查 argc 直接访问 argv[1]
int main(int argc, char *argv[]) {
printf("%s\n", argv[1]); // 若 argc=1,会崩溃!
return 0;
}
// 错误2:误用 argv[0]
int main() { // 缺少参数声明
printf("%s\n", argv[0]); // 编译错误!
return 0;
}
总结
命令行参数是C语言程序与用户交互的基础机制,通过 argc 和 argv 实现灵活的参数传递。掌握以下核心点:
-
参数解析的基本循环结构。
-
选项处理(如
getopt库)。 -
输入验证和错误处理。
-
结合文件操作、数值计算等实际需求。
通过合理设计命令行参数,可以显著提升程序的易用性和功能性。



315

被折叠的 条评论
为什么被折叠?



