指针是C语言的灵魂所在,也是NWAFU-OJ平台上的高频考点。很多同学第一次接触指针时,总觉得它像是一个神秘的黑盒子——明明存储的是地址,却能间接操作数据。让我们从一个经典例题入手,看看指针如何玩转二维数组。
题目要求用指针实现查找二维数组中最大数及其位置。先看常规解法:通过双重循环遍历数组,用变量b记录当前最大值。这种解法直观,但缺乏指针的灵活性。用指针改写后,代码会变得更加简洁高效:
c复制int *p = &a[0][0]; // 将指针指向数组首地址
int max = *p, row = 0, col = 0;
for(int i=0; i<3; i++){
for(int j=0; j<4; j++){
if(*(p + i*4 + j) > max){ // 指针算术运算访问元素
max = *(p + i*4 + j);
row = i; col = j;
}
}
}
这里有几个关键点需要注意:
行号*列数+列号p+1移动的是sizeof(int)个字节*用于获取指针指向的值实际调试时,我建议先用printf输出指针地址和值,观察内存变化。比如在循环中加入:
c复制printf("p+%d=%p, value=%d\n",
i*4+j, p+i*4+j, *(p+i*4+j));
字符串处理是OJ题目的另一个重灾区,而指针在这方面能大显身手。我们来看子字符串提取问题:给定字符串和起始位置,提取指定长度的子串。
新手常犯的错误是直接对数组下标进行操作,这样代码会显得冗长。用指针改写后,逻辑会清晰很多:
c复制char *src = "HelloNWAFU";
char dest[100];
int start = 2, len = 5;
char *p = src + start; // 跳过前start个字符
char *q = dest;
while(len-- > 0 && *p != '\0'){
*q++ = *p++; // 边移动指针边赋值
}
*q = '\0'; // 不要忘记字符串结束符
这段代码展示了指针的典型用法:
在调试字符串问题时,建议:
结构体是组织复杂数据的利器。我们先看NWAFU-OJ上的基础题:定义Person结构体并输入输出成员。很多同学在这里会遇到内存对齐的问题。
c复制struct Person {
char name[20];
char id[10];
float salary;
int age;
};
这个结构体在内存中实际占用的空间可能比你想象的要大。通过sizeof测试会发现:
这是因为编译器会按照最宽成员(float占4字节)进行内存对齐。优化内存的方法包括:
输入结构体数据时,要特别注意:
c复制scanf("%19s %9s %f %d",
p.name, p.id, &p.salary, &p.age);
// 指定宽度防止缓冲区溢出
结构体真正发挥威力是在处理数组时。比如题目要求找出5个员工中薪资最高者,用结构体数组实现非常直观:
c复制struct Employee {
char name[20];
float salary;
} emp[5];
float max_salary = 0;
for(int i=0; i<5; i++){
scanf("%19s %f", emp[i].name, &emp[i].salary);
if(emp[i].salary > max_salary){
max_salary = emp[i].salary;
}
}
在实际项目中,结构体数组常与以下技术配合使用:
一个常见的坑点是结构体赋值问题。直接使用=进行结构体拷贝在某些情况下可能不安全,特别是当结构体包含指针成员时。更安全的做法是使用memcpy或逐个成员赋值。
当指针遇上结构体,就产生了C语言中最强大的工具之一——结构体指针。这在链表、树等数据结构中应用广泛。我们先看一个简单的例子:
c复制typedef struct Node {
int data;
struct Node *next;
} Node;
Node *head = NULL;
// 创建新节点
Node *new_node = (Node*)malloc(sizeof(Node));
new_node->data = 10;
new_node->next = head;
head = new_node;
这段代码展示了链表的头插法。有几个关键点:
在OJ题目中,我经常看到同学们在这些地方出错:
.和->运算符调试链表时,建议编写一个打印函数:
c复制void print_list(Node *p){
while(p != NULL){
printf("%d->", p->data);
p = p->next;
}
printf("NULL\n");
}
函数指针是C语言的高级特性,在NWAFU-OJ的进阶题目中时有出现。比如这个求和函数:
c复制int sum(int (*f)(int), int start, int end){
int s = 0;
for(int i=start; i<=end; i++){
s += f(i); // 通过函数指针调用
}
return s;
}
这个sum函数可以计算任意函数f在区间[start,end]的和。使用时:
c复制int square(int x){ return x*x; }
int cube(int x){ return x*x*x; }
printf("%d\n", sum(square, 1, 10)); // 平方和
printf("%d\n", sum(cube, 1, 10)); // 立方和
函数指针的应用场景包括:
常见错误有:
二维数组的动态分配是OJ中的难点。我们来看矩阵转置题目的通用解法:
c复制int **create_matrix(int n){
int **mat = (int**)malloc(n * sizeof(int*));
for(int i=0; i<n; i++){
mat[i] = (int*)malloc(n * sizeof(int));
}
return mat;
}
void free_matrix(int **mat, int n){
for(int i=0; i<n; i++){
free(mat[i]);
}
free(mat);
}
使用这种封装好的函数可以避免内存泄漏。在调试动态内存程序时:
我见过最隐蔽的错误是:
c复制int *p = malloc(10 * sizeof(int));
p++; // 移动了指针
free(p); // 错误!p不再指向malloc返回的地址
结合前面所有知识点,我们来实现一个简化版的学生管理系统:
c复制typedef struct {
char id[10];
char name[20];
float score[3];
} Student;
void input_student(Student *s){
scanf("%9s %19s", s->id, s->name);
for(int i=0; i<3; i++){
scanf("%f", &s->score[i]);
}
}
void print_student(const Student *s){
printf("%-10s %-20s", s->id, s->name);
for(int i=0; i<3; i++){
printf("%6.1f", s->score[i]);
}
printf("\n");
}
这个案例展示了:
在开发这类系统时,建议: