C语言核心知识点总结与示例
一、核心知识点提炼
🔴 必须掌握(⭐⭐⭐)
1. 结构体的定义与使用
// 定义结构体
struct Student {
int id;
char name[20];
float score;
};
// 使用结构体
struct Student s1 = {1001, "张三", 95.5};
printf("姓名:%s,成绩:%.1f\n", s1.name, s1.score);
// 结构体指针
struct Student* p = &s1;
p->score = 98.0; // 等价于 (*p).score = 98.0
关键点:
-
结构体是"数据容器",组合不同类型
-
.访问成员,->通过指针访问成员 -
结构体可以整体赋值
2. 结构体数组
struct Student class[30]; // 30个学生
// 初始化
struct Student class[3] = {
{1001, "张三", 85.5},
{1002, "李四", 92.0},
{1003, "王五", 78.5}
};
// 遍历
for(int i = 0; i < 3; i++) {
printf("%d\t%s\t%.1f\n", class[i].id, class[i].name, class[i].score);
}
关键点:
-
数组的每个元素是结构体
-
通过下标访问结构体,再用
.访问成员 -
适合管理同类对象集合(如图书、学生)
3. 指针与函数参数
// ❌ 错误:值传递无法修改原数据
void badUpdate(int x) {
x = 100; // 只修改了副本
}
// ✅ 正确:指针传递修改原数据
void goodUpdate(int* x) {
*x = 100; // 修改原变量
}
// ✅ 结构体指针传递(高效)
void updateStudent(struct Student* p, float newScore) {
p->score = newScore; // 直接修改原结构体
}
// 调用
int a = 10;
goodUpdate(&a); // a变成100
updateStudent(&s1, 99.0);
关键点:
-
值传递复制数据,指针传递共享数据
-
传递结构体用指针避免复制(高效)
-
大结构体传指针可节省内存
4. 字符串操作
#include <string.h>
char str1[20] = "Hello";
char str2[20];
// 字符串赋值(不能用=)
strcpy(str2, str1); // str2 = "Hello"
// 字符串比较(不能用==)
if(strcmp(str1, str2) == 0) {
printf("相等\n");
}
// 字符串拼接
strcat(str1, " World"); // str1 = "Hello World"
// 获取长度
int len = strlen(str1); // len = 11
关键点:
-
字符串是字符数组,需要
#include <string.h> -
不能用
=赋值,不能用==比较 -
必须确保目标数组空间足够
🟡 重要掌握(⭐⭐)
5. 函数的声明与定义
// 声明(告诉编译器函数存在)
int add(int a, int b);
void printMsg(const char* msg);
// 定义(实现函数)
int add(int a, int b) {
return a + b;
}
void printMsg(const char* msg) {
printf("%s\n", msg);
}
// const修饰:保护参数不被修改
void showStudent(const struct Student* p) {
// p->score = 100; // ❌ 编译错误,不能修改
printf("%s\n", p->name); // ✅ 可以读取
}
关键点:
-
声明放在主函数之前,定义可以放在后面
-
const保护指针指向的数据不被修改 -
函数名见名知意(如
addBook,deleteBook)
6. 数组删除操作
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
int size = 10;
// 删除索引为3的元素(值为4)
int index = 3;
for(int i = index; i < size - 1; i++) {
arr[i] = arr[i+1]; // 元素前移
}
size--; // 逻辑大小减1
// 结果:1,2,3,5,6,7,8,9,10
关键点:
-
删除后必须维护"逻辑大小"变量
-
用后一个元素覆盖前一个
-
物理数组大小不变,逻辑大小变化
🟢 了解即可(⭐)
7. void* 通用指针
void* ptr; // 可以指向任何类型
int a = 100;
char c = 'A';
ptr = &a; // 指向int
ptr = &c; // 指向char
// 使用时需要强制转换
int* p = (int*)ptr; // 告诉编译器这是int*
二、项目注意事项 ⚠️
🔥 常见错误与避坑指南
1. scanf 输入字符串注意事项
// ❌ 错误:无法输入带空格的字符串
scanf("%s", name); // 输入"Data Structures"只得到"Data"
// ✅ 解决方案1:fgets(推荐)
fgets(name, MAX_NAME, stdin);
// 会包含换行符,需要手动去除
name[strcspn(name, "\n")] = '\0';
// ✅ 解决方案2:正则表达式
scanf("%[^\n]", name); // 读取到换行符为止
2. 数组越界问题
// ❌ 严重错误:数组越界
char name[10];
strcpy(name, "This is a very long name"); // 溢出!
// ✅ 安全做法
strncpy(name, src, sizeof(name) - 1);
name[sizeof(name) - 1] = '\0'; // 确保结尾
3. 指针判空
// ❌ 错误:使用前未检查
struct Book* b = findBookById(id);
b->stock = 10; // 如果b是NULL,程序崩溃
// ✅ 正确:使用前检查
struct Book* b = findBookById(id);
if(b == NULL) {
printf("未找到图书\n");
return;
}
b->stock = 10;
4. 删除操作注意事项
// ⚠️ 删除前必须检查
void deleteBook(int id) {
int index = findBookIndexById(id);
if(index == -1) {
printf("图书不存在\n");
return;
}
// ⚠️ 检查业务规则
if(library[index].borrowed > 0) {
printf("有借出,不能删除\n");
return;
}
// 执行删除
for(int i = index; i < bookCount - 1; i++) {
library[i] = library[i + 1];
}
bookCount--;
}
5. 全局变量管理
// ⚠️ 全局变量的风险:任何函数都能修改
int bookCount = 0; // 全局变量
// ✅ 建议:提供专门的修改函数
int getBookCount() { return bookCount; }
void setBookCount(int count) { bookCount = count; }
三、完整示例:学生成绩管理系统
综合运用所有知识点
#include <stdio.h>
#include <string.h>
#define MAX_STUDENTS 50
#define MAX_NAME 20
// 1. 定义结构体
struct Student {
int id;
char name[MAX_NAME];
float score;
};
// 2. 全局数据
struct Student students[MAX_STUDENTS];
int studentCount = 0;
int nextId = 1001;
// 3. 函数声明
void addStudent(const char* name, float score);
struct Student* findStudent(int id);
void updateScore(int id, float newScore);
void deleteStudent(int id);
void listAll();
void showMenu();
// 4. 主函数
int main() {
// 测试数据
addStudent("张三", 85.5);
addStudent("李四", 92.0);
addStudent("王五", 78.5);
int choice;
while(1) {
showMenu();
scanf("%d", &choice);
if(choice == 1) {
char name[MAX_NAME];
float score;
printf("姓名:");
scanf("%s", name);
printf("成绩:");
scanf("%f", &score);
addStudent(name, score);
} else if(choice == 2) {
int id;
float newScore;
printf("学号:");
scanf("%d", &id);
printf("新成绩:");
scanf("%f", &newScore);
updateScore(id, newScore);
} else if(choice == 3) {
int id;
printf("学号:");
scanf("%d", &id);
deleteStudent(id);
} else if(choice == 4) {
listAll();
} else if(choice == 5) {
printf("再见!\n");
break;
}
}
return 0;
}
// 5. 函数实现
void showMenu() {
printf("\n=== 学生成绩管理系统 ===\n");
printf("学生总数:%d\n", studentCount);
printf("1. 添加学生\n");
printf("2. 修改成绩\n");
printf("3. 删除学生\n");
printf("4. 显示所有\n");
printf("5. 退出\n");
printf("请选择:");
}
void addStudent(const char* name, float score) {
if(studentCount >= MAX_STUDENTS) {
printf("❌ 学生已满!\n");
return;
}
struct Student* s = &students[studentCount];
s->id = nextId++;
strcpy(s->name, name);
s->score = score;
studentCount++;
printf("✅ 添加成功!学号:%d\n", s->id);
}
struct Student* findStudent(int id) {
for(int i = 0; i < studentCount; i++) {
if(students[i].id == id) {
return &students[i];
}
}
return NULL;
}
void updateScore(int id, float newScore) {
struct Student* s = findStudent(id);
if(s == NULL) {
printf("❌ 未找到学号 %d\n", id);
return;
}
if(newScore < 0 || newScore > 100) {
printf("❌ 成绩必须在0-100之间\n");
return;
}
s->score = newScore;
printf("✅ 成绩已更新为 %.1f\n", newScore);
}
void deleteStudent(int id) {
int index = -1;
for(int i = 0; i < studentCount; i++) {
if(students[i].id == id) {
index = i;
break;
}
}
if(index == -1) {
printf("❌ 未找到学号 %d\n", id);
return;
}
// 删除操作:前移覆盖
for(int i = index; i < studentCount - 1; i++) {
students[i] = students[i + 1];
}
studentCount--;
printf("✅ 删除成功!\n");
}
void listAll() {
if(studentCount == 0) {
printf("📭 暂无学生\n");
return;
}
printf("\n学号\t姓名\t成绩\n");
printf("====================\n");
for(int i = 0; i < studentCount; i++) {
struct Student* s = &students[i];
printf("%d\t%s\t%.1f\n", s->id, s->name, s->score);
}
printf("====================\n");
printf("共计 %d 名学生\n", studentCount);
}
四、知识图谱速查表
| 知识点 | 关键语法 | 常见错误 | 记忆口诀 |
|---|---|---|---|
| 结构体 | struct Book { }; |
忘记分号 | "数据打包成类型" |
| 结构体数组 | struct Book books[100]; |
数组越界 | "连续存储如书架" |
| 指针访问 | p->member |
混淆.和-> |
"指针用箭头" |
| 字符串复制 | strcpy(dest, src) |
空间不足 | "不能直接赋值" |
| 字符串比较 | strcmp(s1, s2) == 0 |
用==比较 |
"比较用函数" |
| 指针传参 | void f(int* p) |
忘加& |
"想修改用指针" |
| 数组删除 | 覆盖前移 | 忘记更新数量 | "后往前覆盖" |
| 错误检查 | if(ptr == NULL) |
使用前不检查 | "用前先判空" |
五、建议
-
理解内存布局:结构体在内存中如何存储
-
学会调试:使用GDB查看指针和结构体内容