c语言项目驱动学习--实例化--001-V1基础知识

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) 使用前不检查 "用前先判空"

五、建议

  1. 理解内存布局:结构体在内存中如何存储

  2. 学会调试:使用GDB查看指针和结构体内容