引言:C语言在理工科教育中的核心地位

C语言作为计算机科学与技术领域的基石性编程语言,长期以来在理工学院的教学体系中占据着不可替代的重要地位。它不仅是计算机专业学生入门的首选语言,更是电子工程、自动化、机械工程等众多理工科专业学生必须掌握的核心技能。本教学大纲旨在为理工学院学生提供一套从零基础开始,循序渐进,最终达到能够独立完成中等规模项目实战能力的完整学习路径。

一、课程基本信息与定位

1.1 课程名称与代码

  • 课程名称:C语言程序设计
  • 课程代码:CS101(计算机类)/ EE101(电子工程类)
  • 课程性质:专业基础必修课
  • 学时分配:理论教学48学时 + 实验教学32学时 = 总计80学时
  • 学分:5学分

1.2 先修课程与依赖关系

本课程假设学生具备以下基础:

  • 高中数学知识(特别是逻辑思维和代数运算)
  • 基本的计算机操作能力(Windows/Linux操作系统使用)
  • 无需编程经验,从零开始教学

1.3 适用专业范围

  • 计算机科学与技术
  • 软件工程
  • 电子信息工程
  • 自动化
  • 机械电子工程
  • 物联网工程
  • 生物医学工程

二、课程目标体系详解

2.1 总体目标

通过本课程的学习,使学生能够:

  1. 掌握C语言的基本语法和编程规范
  2. 理解程序设计的基本思想和算法逻辑
  3. 具备独立分析和解决中等复杂度问题的能力
  4. 能够使用C语言开发小型应用程序和系统工具
  5. 为后续学习数据结构、操作系统、嵌入式系统等课程打下坚实基础

2.2 分层目标详解

2.2.1 知识目标(Knowledge Objectives)

  • 基础语法层:熟练掌握数据类型、运算符、表达式、输入输出等基础语法
  • 控制结构层:精通顺序、选择、循环三种基本控制结构
  • 函数模块层:理解函数定义、调用、参数传递、递归等概念
  • 数据组织层:掌握数组、字符串、结构体、共用体、枚举等数据类型
  • 指针核心层:深入理解指针概念,掌握指针与数组、函数、结构体的结合使用
  • 高级应用层:掌握文件操作、动态内存管理、预处理器指令等高级特性

2.2.2 能力目标(Ability Objectives)

  • 代码阅读能力:能够快速理解他人编写的C语言代码
  • 调试排错能力:熟练使用调试工具定位和修复程序错误
  • 算法实现能力:能够将数学问题和逻辑问题转化为C语言程序
  • 项目架构能力:具备设计和开发多文件项目的能力
  • 性能优化意识:理解时间复杂度和空间复杂度的基本概念

2.2.3 素质目标(Quality Objectives)

  • 工程思维:培养严谨的逻辑思维和工程化编程习惯
  • 规范意识:养成良好的代码风格和文档编写习惯
  • 创新精神:鼓励学生在项目中尝试创新解决方案
  • 协作能力:通过团队项目培养协作开发能力

三、教学内容与进度安排(16周)

第1-2周:C语言入门与基础语法(零基础起步)

教学内容

  • C语言历史、特点与应用领域
  • 开发环境搭建(GCC/Clang/Visual Studio)
  • 第一个C程序:Hello World详解
  • 数据类型详解:整型、浮点型、字符型
  • 常量与变量:定义、初始化、作用域
  • 基本输入输出函数:printf/scanf/getchar/putchar

实验内容

// 实验1:温度转换程序
// 题目:输入摄氏温度,转换为华氏温度并输出
// 公式:F = C × 9/5 + 32

#include <stdio.h>

int main() {
    float celsius, fahrenheit;
    
    printf("请输入摄氏温度(例如:25.5):");
    scanf("%f", &celsius);
    
    fahrenheit = celsius * 9.0/5.0 + 32;
    
    printf("%.1f摄氏度 = %.1f华氏度\n", celsius, fahrenheit);
    
    return 0;
}

代码解析

  • #include <stdio.h>:包含标准输入输出头文件
  • float:定义浮点数类型变量
  • scanf:从键盘读取输入,注意&取地址符的使用
  • %.1f:格式化输出,保留1位小数

学习重点

  • 理解程序的基本结构
  • 掌握变量的定义与使用
  • 熟悉基本的输入输出操作
  • 培养编程的第一感觉

第3-4周:运算符、表达式与程序控制流

教学内容

  • 算术运算符、关系运算符、逻辑运算符
  • 赋值运算符、位运算符、条件运算符
  • 运算符优先级与结合性
  • if-else选择结构
  • switch-case多分支选择结构
  • for/while/do-while循环结构
  • break、continue、goto语句

实验内容

// 实验2:成绩等级判定程序
// 题目:输入百分制成绩,输出对应的等级(A/B/C/D/E)
// 规则:90-100:A, 80-89:B, 70-79:C, 60-69:D, <60:E

#include <stdio.h>

int main() {
    int score;
    char grade;
    
    printf("请输入成绩(0-100):");
    scanf("%d", &score);
    
    // 输入验证
    if (score < 0 || score > 100) {
        printf("输入错误!成绩必须在0-100之间。\n");
        return 1;
    }
    
    // 使用if-else实现
    if (score >= 90) {
        grade = 'A';
    } else if (score >= 80) {
        grade = 'B';
    } else if (score >= 70) {
        grade = 'C';
    } else if (score >= 100) {
        grade = 'D';
    } else {
        grade = 'E';
    }
    
    printf("成绩:%d,等级:%c\n", score, grade);
    
    // 使用switch-case实现(另一种方法)
    switch (score / 10) {
        case 10:
        case 9:  printf("等级:A\n"); break;
        case 8:  printf("等级:B\n"); break;
        case 7:  printf("等级:C\n"); break;
        case 6:  printf("等级:D\n"); break;
        default: printf("等级:E\n"); break;
    }
    
    return 0;
}

代码解析

  • if-else:多条件分支判断,注意||逻辑或的使用
  • switch-case:多分支选择,注意break的重要性
  • 输入验证:防止非法输入导致程序异常
  • 整数除法:score / 10自动取整,用于分类

学习重点

  • 理解不同运算符的优先级
  • 掌握条件判断的逻辑表达
  • 理解循环的执行流程
  • 培养边界条件思考习惯

第5-6周:函数与模块化编程

教学内容

  • 函数的定义、声明与调用
  • 形参与实参、值传递与引用传递
  • 函数的嵌套调用与递归调用
  • 变量的作用域与存储类别
  • 内部函数与外部函数
  • 头文件的设计与使用

实验内容

// 实验3:模块化计算器程序
// 题目:使用函数实现加减乘除四则运算
// 要求:主函数只负责输入输出,计算逻辑封装在函数中

#include <stdio.h>

// 函数声明
float add(float a, float b);
float subtract(float a, float b);
float multiply(float a, float b);
float divide(float a, float b);
void displayMenu();

int main() {
    int choice;
    float num1, num2, result;
    
    while (1) {
        displayMenu();
        printf("请选择操作(1-4,0退出):");
        scanf("%d", &choice);
        
        if (choice == 0) {
            printf("感谢使用计算器!\n");
            break;
        }
        
        if (choice < 1 || choice > 4) {
            printf("无效选择!\n");
            continue;
        }
        
        printf("请输入两个数(用空格分隔):");
        scanf("%f %f", &num1, &num2);
        
        switch (choice) {
            case 1: result = add(num1, num2); break;
            case 2: result = subtract(num1, num2); break;
            case 3: result = multiply(num1, num2); break;
            case 4: 
                if (num2 == 0) {
                    printf("错误:除数不能为0!\n");
                    continue;
                }
                result = divide(num1, num2); 
                break;
        }
        
        printf("结果:%.2f\n\n", result);
    }
    
    return 0;
}

// 函数定义
float add(float a, float b) {
    return a + b;
}

float subtract(float a, float b) {
    return a - b;
}

float multiply(float a, float b) {
    return a * b;
}

float divide(float a, float b) {
    return a / b;
}

void displayMenu() {
    printf("========== 计算器 ==========\n");
    printf("1. 加法\n");
    printf("2. 减法\n");
   �  printf("3. 乘法\n");
    printf("4. 除法\n");
    printf("0. 退出\n");
    printf("===========================\n");
}

代码解析

  • 函数声明:在main函数前声明所有函数,避免编译警告
  • 模块化设计:每个函数只做一件事,职责清晰
  • 参数传递:浮点数作为参数传递,理解值传递机制
  • 错误处理:除法运算前检查除数是否为0
  • 循环菜单:使用while(1)实现持续交互

学习重点

  • 理解函数作为代码复用单元的意义
  • 掌握参数传递的机制
  • 理解递归的思想(斐波那契数列、阶乘)
  • 培养模块化编程思维

第7-8周:数组与字符串

教学内容

  • 一维数组、二维数组的定义与使用
  • 数组作为函数参数传递
  • 字符串与字符数组
  • 常用字符串处理函数(strlen, strcpy, strcmp, strcat)
  • 字符串输入输出(gets, puts, fgets)

实验内容

// 实验4:学生成绩管理系统(基础版)
// 题目:输入N个学生的成绩,计算平均分、最高分、最低分
// 并统计各分数段人数(优秀:90+, 良好:80-89, 中等:70-79, 及格:60-69, 不及格:<60)

#include <stdio.h>
#define MAX_STUDENTS 50
#define N 5  // 学生人数

void inputScores(int scores[], int n);
float calculateAverage(int scores[], int n);
int findMax(int scores[], int n);
int findMin(int scores[], int n);
void countGrades(int scores[], int n, int* excellent, int* good, int* medium, int* pass, int* fail);

int main() {
    int scores[MAX_STUDENTS];
    int excellent = 0, good = 0, medium = 0, pass = 0, fail = 0;
    
    inputScores(scores, N);
    
    printf("\n========== 统计结果 ==========\n");
    printf("平均分:%.2f\n", calculateAverage(scores, N));
    printf("最高分:%d\n", findMax(scores, N));
    printf("最低分:%d\n", findMin(scores, N));
    
    countGrades(scores, N, &excellent, &good, &medium, &pass, &fail);
    
    printf("\n========== 分数段统计 ==========\n");
    printf("优秀(90-100):%d人\n", excellent);
    printf("良好(80-89):%d人\n", good);
    printf("中等(70-79):%d人\n", medium);
    printf("及格(60-69):%d人\n", pass);
    printf("不及格(<60):%d人\n", fail);
    
    return 0;
}

void inputScores(int scores[], int n) {
    printf("请输入%d个学生的成绩(0-100):\n", n);
    for (int i = 0; i < n; i++) {
        printf("学生%d:", i + 1);
        scanf("%d", &scores[i]);
        // 输入验证
        while (scores[i] < 0 || scores[i] > 100) {
            printf("输入错误!请重新输入(0-100):");
            scanf("%d", &scores[i]);
        }
    }
}

float calculateAverage(int scores[], int n) {
    int sum = 0;
    for (int i = 0; i < n; i++) {
        sum += scores[i];
    }
    return (float)sum / n;
}

int findMax(int scores[], int n) {
    int max = scores[0];
    for (int i = 1; i < n; i++) {
        if (scores[i] > max) {
            1  max = scores[i];  // 更新最大值
        }
    }
    return max;
}

int findMin(int scores[], int n) {
    int min = scores[0];
    for (int i = 1; i < n; i++) {
        if (scores[i] < min) {
            min = scores[i];  // 更新最小值
        }
    }
    return min;
}

void countGrades(int scores[], int n, int* excellent, int* good, int* medium, int* pass, int* fail) {
    for (int i = 0; i < n; i++) {
        if (scores[i] >= 90) {
            (*excellent)++;
        } else if (scores[i] >= 80) {
            (*good)++;
        } else if (scores[i] >= 70) {
            (*medium)++;
        } else if (scores[i] >= 60) {
            (*pass)++;
        } else {
            (*fail)++;
        }
    }
}

代码解析

  • 数组作为参数:数组名作为参数传递的是数组首地址
  • 指针参数:使用指针修改函数外部变量的值
  • 循环统计:使用if-else链进行分类统计
  • 边界处理:输入时验证数据合法性

学习重点

  • 理解数组在内存中的连续存储特性
  • 掌握数组越界的危害与防范
  • 理解字符串作为字符数组的特殊性
  • 掌握字符串处理函数的使用

第9-10周:指针深入理解

教学内容

  • 指针的基本概念与定义
  • 指针运算与指针算术
  • 指针与数组的关系
  • 指针作为函数参数(指针传递)
  • 指针数组与数组指针
  • 多级指针(指向指针的指针)
  • 指针与字符串

实验内容

// 实验5:使用指针实现字符串处理函数
// 题目:自己实现strlen, strcpy, strcmp, strcat函数

#include <stdio.h>
#include <stdlib.h>

// 计算字符串长度
int my_strlen(const char* str) {
    int len = 0;
    while (str[len] != '\0') {
        len++;
    }
    return len;
}

// 字符串复制
void my_strcpy(char* dest, const char* src) {
    while (*dest++ = *src++) {
        // 循环体为空,复制包括'\0'在内的所有字符
    }
}

// 字符串比较
int my_strcmp(const char* str1, const char* str2) {
    while (*str1 && (*str1 == *str2)) {
        str1++;
        str2++;
    }
    return *(unsigned char*)str1 - *(unsigned char*)str2;
}

// 字符串连接
void my_strcat(char* dest, const char* src) {
    // 先找到dest的末尾
    while (*dest) {
        dest++;
    }
    // 再复制src
    while (*dest++ = *src++) {
        // 复制包括'\0'在内的所有字符
    }
}

int main() {
    char str1[50] = "Hello";
    char str2[50] = "World";
    char str3[50];
    
    printf("原始字符串:str1=\"%s\", str2=\"%s\"\n", str1, str2);
    
    // 测试strlen
    printf("\nstrlen测试:\n");
    printf("str1长度:%d\n", my_strlen(str1));
    printf("str2长度:%d\n", my_strlen(str2));
    
    // 测试strcpy
    printf("\nstrcpy测试:\n");
    my_strcpy(str3, str1);
    printf("复制后str3=\"%s\"\n", str3);
    
    // 测试strcat
    printf("\nstrcat测试:\n");
    my_strcat(str1, " ");
    my_strcat(str1, str2);
    printf("连接后str1=\"%s\"\n", str1);
    
    // 测试strcmp
    printf("\nstrcmp测试:\n");
    printf("str1和str2比较结果:%d\n", my_strcmp(str1, str2));
    printf("str1和str3比较结果:%d\n", my_strcmp(str1, str3));
    
    return 0;
}

代码解析

  • const char*:使用const保护源字符串不被修改
  • 指针遍历:通过指针移动访问字符串每个字符
  • while (*dest++ = *src++):经典写法,利用赋值表达式的返回值
  • 空循环体:利用C语言的语法特性简化代码
  • 指针比较:返回值的正负表示比较结果

学习重点

  • 理解指针就是地址的概念
  • 掌握指针运算的规则
  • 理解指针与数组的等价关系
  • 掌握指针作为函数参数的强大功能

第11-12周:结构体、共用体与枚举

教学内容

  • 结构体的定义与初始化
  • 结构体变量的使用
  • 结构体数组
  • 结构体指针
  • 结构体作为函数参数
  • 共用体(union)的概念与使用
  • 枚举类型(enum)的定义与使用
  • typedef的使用

实验内容

// 实验6:学生信息管理系统(完整版)
// 题目:使用结构体存储学生信息,包括学号、姓名、三门课程成绩
// 功能:添加学生、显示所有学生、计算平均分、按成绩排序

#include <stdio.h>
#include <string.h>
#define MAX_STUDENTS 100
#define COURSE_NUM 3

// 学生结构体
typedef struct {
    char id[20];
    char name[30];
    int scores[COURSE_NUM];
    float average;
} Student;

// 全局变量
Student students[MAX_STUDENTS];
int studentCount = 0;

// 函数声明
void addStudent();
void displayAll();
void calculateAverage();
void sortByAverage();
void showMenu();

int main() {
    int choice;
    
    while (1) {
        showMenu();
        printf("请选择操作:");
        scanf("%d", &choice);
        
        switch (choice) {
            case 1:
                addStudent();
                break;
            case 2:
                displayAll();
                break;
            case 3:
                calculateAverage();
                break;
            case 4:
                sortByAverage();
                break;
            case 0:
                printf("程序退出,谢谢使用!\n");
                return 0;
            default:
                printf("无效选择!\n");
        }
    }
    
    return 0;
}

void addStudent() {
    if (studentCount >= MAX_STUDENTS) {
        printf("学生数量已达上限!\n");
        return;
    }
    
    Student* s = &students[studentCount];
    
    printf("\n请输入学号:");
    scanf("%s", s->id);
    printf("请输入姓名:");
    scanf("%s", s->name);
    
    printf("请输入%d门课程成绩:\n", COURSE_NUM);
    for (int i = 0; i < COURSE_NUM; i++) {
        printf("课程%d:", i + 1);
        scanf("%d", &s->scores[i]);
    }
    
    // 计算平均分
    int sum = 0;
    for (int i = 0; i < COURSE_NUM; i++) {
        sum += s->scores[i];
    }
    s->average = (float)sum / COURSE_NUM;
    
    studentCount++;
    printf("学生添加成功!\n");
}

void displayAll() {
    if (studentCount == 0) {
        printf("暂无学生信息!\n");
        return;
    }
    
    printf("\n%-15s %-10s", "学号", "姓名");
    for (int i = 0; i < COURSE_NUM; i++) {
        printf(" 课程%d", i + 1);
    }
    printf(" 平均分\n");
    printf("--------------------------------------------------------\n");
    
    for (int i = 0; i < studentCount; i++) {
        printf("%-15s %-10s", students[i].id, students[i].name);
        for (int j = 0; j < COURSE_NUM; j++) {
            printf(" %-6d", students[i].scores[j]);
        }
        printf(" %-6.2f\n", students[i].average);
    }
}

void calculateAverage() {
    if (studentCount == 0) {
        printf("暂无学生信息!\n");
        return;
    }
    
    float classAverage = 0;
    for (int i = 0; i < studentCount; i++) {
        classAverage += students[i].average;
    }
    classAverage /= studentCount;
    
    printf("\n班级平均分:%.2f\n", classAverage);
}

void sortByAverage() {
    if (studentCount == 0) {
        printf("暂无学生信息!\n");
        return;
    }
    
    // 使用冒泡排序
    for (int i = 0; i < studentCount - 1; i++) {
        for (int j = 0; j < studentCount - 1 - i; j++) {
            if (students[j].average < students[j + 1].average) {
                // 交换两个结构体
                Student temp = students[j];
                students[j] = students[j + 1];
                students[j + 1] = temp;
            }
        }
    }
    
    printf("按平均分降序排序完成!\n");
    displayAll();
}

void showMenu() {
    printf("\n========== 学生信息管理系统 ==========\n");
    printf("1. 添加学生\n");
    printf("2. 显示所有学生\n");
    printf("3. 计算班级平均分\n");
    printf("4. 按平均分排序\n");
    printf("0. 退出\n");
    printf("=====================================\n");
}

代码解析

  • typedef:为结构体创建别名,简化使用
  • 结构体数组:存储多个学生信息
  • 结构体指针:Student* s = &students[studentCount],使用指针访问成员
  • s->id:箭头运算符,等价于(*s).id
  • 结构体赋值:可以直接赋值,实现交换
  • 格式化输出:%-15s表示左对齐,宽度15

学习重点

  • 理解结构体作为数据封装单元的意义
  • 掌握结构体成员的访问方式
  • 理解结构体数组的应用
  • 掌握结构体在复杂数据管理中的使用

第13周:文件操作

教学内容

  • 文件指针与文件的打开/关闭
  • 文件的顺序读写(fscanf/fprintf, fgets/fputs)
  • 文件的随机读写(fseek, ftell)
  • 二进制文件与文本文件
  • 文件操作的错误处理

实验内容

// 实验7:学生信息持久化存储
// 题目:将实验6的学生信息保存到文件,并能从文件读取

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#define MAX_STUDENTS 100
#define COURSE_NUM 3
#define FILENAME "students.dat"

typedef struct {
    char id[20];
    char name[30];
    int scores[COURSE_NUM];
    float average;
} Student;

Student students[MAX_STUDENTS];
int studentCount = 0;

// 保存到文件(文本格式)
void saveToFile() {
    FILE* fp = fopen(FILENAME, "w");
    if (fp == NULL) {
        printf("无法打开文件进行写入!\n");
        return;
    }
    
    // 写入学生数量
    fprintf(fp, "%d\n", studentCount);
    
    // 写入每个学生信息
    for (int i = 0; i < studentCount; i++) {
        fprintf(fp, "%s %s ", students[i].id, students[i].name);
        for (int j = 0; j < COURSE_NUM; j++) {
            fprintf(fp, "%d ", students[i].scores[j]);
        }
        fprintf(fp, "%.2f\n", students[i].average);
    }
    
    fclose(fp);
    printf("数据已保存到 %s\n", FILENAME);
}

// 从文件读取
void loadFromFile() {
    FILE* fp = fopen(FILENAME, "r");
    if (fp == NULL) {
        printf("文件不存在或无法打开,将创建新文件。\n");
        return;
    }
    
    // 读取学生数量
    if (fscanf(fp, "%d", &studentCount) != 1) {
        printf("文件格式错误!\n");
        fclose(fp);
        return;
    }
    
    // 读取每个学生信息
    for (int i = 0; i < studentCount; i++) {
        fscanf(fp, "%s %s", students[i].id, students[i].name);
        for (int j = 0; j < COURSE_NUM; j++) {
            fscanf(fp, "%d", &students[i].scores[j]);
        }
        fscanf(fp, "%f", &students[i].average);
    }
    
    fclose(fp);
    printf("已从 %s 加载 %d 条学生记录\n", FILENAME, studentCount);
}

// 保存到文件(二进制格式)
void saveToFileBinary() {
    FILE* fp = fopen("students.bin", "wb");
    if (fp == NULL) {
        printf("无法打开二进制文件!\n");
        return;
    }
    
    // 写入学生数量
    fwrite(&studentCount, sizeof(int), 1, fp);
    
    // 写入所有学生数据
    fwrite(students, sizeof(Student), studentCount, fp);
    
    fclose(fp);
    printf("数据已保存到二进制文件 students.bin\n");
}

// 从二进制文件读取
void loadFromFileBinary() {
    FILE* fp = fopen("students.bin", "rb");
    if (fp == NULL) {
        printf("二进制文件不存在!\n");
        return;
    }
    
    // 读取学生数量
    fread(&studentCount, sizeof(int), 1, fp);
    
    // 读取所有学生数据
    fread(students, sizeof(Student), studentCount, fp);
    
    fclose(fp);
    printf("已从二进制文件加载 %d 条学生记录\n", studentCount);
}

int main() {
    // 主函数保持不变,添加文件操作菜单
    loadFromFile();  // 程序启动时自动加载
    
    // ... 原有的菜单和操作逻辑 ...
    
    // 退出时保存
    saveToFile();
    
    return 0;
}

代码解析

  • FILE*:文件指针,用于操作文件
  • fopen:打开文件,模式"w"(写)、"r"(读)、"wb"(二进制写)
  • fprintf/fscanf:格式化读写,类似printf/scanf
  • fwrite/fread:二进制读写,按内存块读写
  • fclose:必须关闭文件,释放资源
  • 错误检查:检查文件指针是否为NULL

学习重点

  • 理解文件作为外部存储的意义
  • 掌握文件打开模式的选择
  • 理解文本文件与二进制文件的区别
  • 掌握文件读写的错误处理

第14周:动态内存管理

教学内容

  • 内存分区(栈、堆、静态区)
  • malloc、calloc、realloc函数
  • free函数与内存释放
  • 内存泄漏的概念与防范
  • 动态数组的实现
  • 链表基础(简介)

实验内容

// 实验8:动态数组学生管理系统
// 题目:使用动态内存分配实现可变长度的学生数组

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char id[20];
    char name[30];
    float score;
} Student;

// 动态添加学生
void addStudentDynamic(Student** students, int* count, int* capacity) {
    // 检查容量,需要时扩容
    if (*count >= *capacity) {
        *capacity = (*capacity == 0) ? 2 : (*capacity) * 2;
        Student* newStudents = (Student*)realloc(*students, *capacity * sizeof(Student));
        if (newStudents == NULL) {
            printf("内存分配失败!\n");
            exit(1);
        }
        *students = newStudents;
        printf("容量已扩展到 %d\n", *capacity);
    }
    
    // 添加新学生
    Student* s = &(*students)[*count];
    printf("请输入学号:");
    scanf("%s", s->id);
    printf("请输入姓名:");
    scanf("%s", s->name);
    printf("请输入成绩:");
    scanf("%f", &s->score);
    
    (*count)++;
    printf("学生添加成功!\n");
}

// 显示所有学生
void displayAllDynamic(Student* students, int count) {
    if (count == 0) {
        printf("暂无学生信息!\n");
        return;
    }
    
    printf("\n%-15s %-10s %-6s\n", "学号", "姓名", "成绩");
    printf("--------------------------------\n");
    for (int i = 0; i < count; i++) {
        printf("%-15s %-10s %-6.2f\n", students[i].id, students[i].name, students[i].score);
    }
}

// 释放内存
void freeStudents(Student** students, int* count, int* capacity) {
    free(*students);
    *students = NULL;
    *count = 0;
    *capacity = 0;
    printf("内存已释放!\n");
}

int main() {
    Student* students = NULL;  // 动态数组指针
    int count = 0;             // 当前学生数量
    int capacity = 0;          // 当前容量
    
    int choice;
    
    while (1) {
        printf("\n========== 动态学生管理系统 ==========\n");
        printf("1. 添加学生\n");
        printf("2. 显示所有\n");
        printf("3. 释放内存并退出\n");
        printf("请选择:");
        scanf("%d", &choice);
        
        switch (choice) {
            case 1:
                addStudentDynamic(&students, &count, &capacity);
                break;
            case 2:
                displayAllDynamic(students, count);
                break;
            case 3:
                freeStudents(&students, &count, &capacity);
                return 0;
            default:
                printf("无效选择!\n");
        }
    }
    
    return 0;
}

代码解析

  • Student**:指向指针的指针,用于修改外部指针
  • realloc:动态调整数组大小,保留原有数据
  • malloc:首次分配内存
  • free:释放内存,防止泄漏
  • 指针传递:需要修改指针本身时,必须传递指针的地址

学习重点

  • 理解栈内存与堆内存的区别
  • 掌握动态内存分配函数的使用
  • 理解内存泄漏的危害
  • 掌握动态数组的实现方法

第15-16周:项目实战与综合复习

实战项目:简易银行账户管理系统

// 项目:银行账户管理系统
// 功能:开户、存款、取款、查询、转账、交易记录、文件持久化

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

#define MAX_ACCOUNTS 100
#define MAX_TRANSACTIONS 50

// 交易记录结构体
typedef struct {
    time_t timestamp;
    char type[20];  // "存款"、"取款"、"转账"
    double amount;
    char description[100];
} Transaction;

// 账户结构体
typedef struct {
    char accountNumber[20];
    char password[20];
    char name[30];
    double balance;
    Transaction transactions[MAX_TRANSACTIONS];
    int transactionCount;
} Account;

// 全局变量
Account accounts[MAX_ACCOUNTS];
int accountCount = 0;

// 函数声明
void createAccount();
void deposit();
void withdraw();
void transfer();
void queryBalance();
void viewTransactions();
void saveData();
void loadData();
void showMainMenu();
void showTransactionMenu();

int main() {
    loadData();
    
    int choice;
    while (1) {
        showMainMenu();
        printf("请选择操作:");
        scanf("%d", &choice);
        
        switch (choice) {
            case 1: createAccount(); break;
            case 2: deposit(); break;
            case 3: withdraw(); break;
            case 4: transfer(); break;
            case 5: queryBalance(); break;
            case 6: viewTransactions(); break;
            case 7: saveData(); break;
            case 0: 
                saveData();
                printf("感谢使用,再见!\n");
                return 0;
            default: printf("无效选择!\n");
        }
    }
    
    return 0;
}

void createAccount() {
    if (accountCount >= MAX_ACCOUNTS) {
        printf("账户数量已达上限!\n");
        return;
    }
    
    Account* acc = &accounts[accountCount];
    
    printf("\n=== 开户 ===\n");
    printf("请输入账号:");
    scanf("%s", acc->accountNumber);
    printf("请输入密码:");
    scanf("%s", acc->password);
    printf("请输入姓名:");
    scanf("%s", acc->name);
    
    acc->balance = 0;
    acc->transactionCount = 0;
    
    accountCount++;
    printf("开户成功!您的初始余额为:0.00\n");
}

int findAccount(const char* accountNumber, const char* password) {
    for (int i = 0; i < accountCount; i++) {
        if (strcmp(accounts[i].accountNumber, accountNumber) == 0 &&
            strcmp(accounts[i].password, password) == 0) {
            return i;  // 返回账户索引
        }
    }
    return -1;  // 未找到
}

void addTransaction(int index, const char* type, double amount, const char* description) {
    if (accounts[index].transactionCount >= MAX_TRANSACTIONS) {
        // 删除最早的记录
        for (int i = 0; i < MAX_TRANSACTIONS - 1; i++) {
            accounts[index].transactions[i] = accounts[index].transactions[i + 1];
        }
        accounts[index].transactionCount--;
    }
    
    Transaction* t = &accounts[index].transactions[accounts[index].transactionCount];
    t->timestamp = time(NULL);
    strcpy(t->type, type);
    t->amount = amount;
    strcpy(t->description, description);
    
    accounts[index].transactionCount++;
}

void deposit() {
    char accountNumber[20], password[20];
    double amount;
    
    printf("\n=== 存款 ===\n");
    printf("请输入账号:");
    scanf("%s", accountNumber);
    printf("请输入密码:");
    scanf("%s", password);
    
    int index = findAccount(accountNumber, password);
    if (index == -1) {
        printf("账号或密码错误!\n");
        return;
    }
    
    printf("当前余额:%.2f\n", accounts[index].balance);
    printf("请输入存款金额:");
    scanf("%lf", &amount);
    
    if (amount <= 0) {
        printf("存款金额必须大于0!\n");
        return;
    }
    
    accounts[index].balance += amount;
    addTransaction(index, "存款", amount, "现金存款");
    
    printf("存款成功!当前余额:%.2f\n", accounts[index].balance);
}

void withdraw() {
    char accountNumber[20], password[20];
    double amount;
    
    printf("\n=== 取款 ===\n");
    printf("请输入账号:");
    scanf("%s", accountNumber);
    printf("请输入密码:");
    scanf("%s", password);
    
    int index = findAccount(accountNumber, password);
    if (index == -1) {
        printf("账号或密码错误!\n");
        return;
    }
    
    printf("当前余额:%.2f\n", accounts[index].balance);
    printf("请输入取款金额:");
    scanf("%lf", &amount);
    
    if (amount <= 0) {
        printf("取款金额必须大于0!\n");
        return;
    }
    
    if (amount > accounts[index].balance) {
        printf("余额不足!\n");
        return;
    }
    
    accounts[index].balance -= amount;
    addTransaction(index, "取款", amount, "ATM取款");
    
    printf("取款成功!当前余额:%.2f\n", accounts[index].balance);
}

void transfer() {
    char fromAccount[20], fromPassword[20], toAccount[20];
    double amount;
    
    printf("\n=== 转账 ===\n");
    printf("请输入转出账号:");
    scanf("%s", fromAccount);
    printf("请输入密码:");
    scanf("%s", fromPassword);
    
    int fromIndex = findAccount(fromAccount, fromPassword);
    if (fromIndex == -1) {
        printf("账号或密码错误!\n");
        return;
    }
    
    printf("请输入转入账号:");
    scanf("%s", toAccount);
    
    int toIndex = -1;
    for (int i = 0; i < accountCount; i++) {
        if (strcmp(accounts[i].accountNumber, toAccount) == 0) {
            toIndex = i;
            break;
        }
    }
    
    if (toIndex == -1) {
        printf("转入账号不存在!\n");
        return;
    }
    
    printf("请输入转账金额:");
    scanf("%lf", &amount);
    
    if (amount <= 0) {
        printf("转账金额必须大于0!\n");
        return;
    }
    
    if (amount > accounts[fromIndex].balance) {
        printf("余额不足!\n");
        return;
    }
    
    accounts[fromIndex].balance -= amount;
    accounts[toIndex].balance += amount;
    
    char desc[100];
    sprintf(desc, "转账给%s", accounts[toIndex].name);
    addTransaction(fromIndex, "转账", amount, desc);
    
    sprintf(desc, "从%s转账", accounts[fromIndex].name);
    addTransaction(toIndex, "转账", amount, desc);
    
    printf("转账成功!\n");
    printf("转出方余额:%.2f\n", accounts[fromIndex].balance);
    printf("转入方余额:%.2f\n", accounts[toIndex].balance);
}

void queryBalance() {
    char accountNumber[20], password[20];
    
    printf("\n=== 查询余额 ===\n");
    printf("请输入账号:");
    scanf("%s", accountNumber);
    printf("请输入密码:");
    scanf("%s", password);
    
    int index = findAccount(accountNumber, password);
    if (index == -1) {
        printf("账号或密码错误!\n");
        return;
    }
    
    printf("\n账户信息:\n");
    printf("账号:%s\n", accounts[index].accountNumber);
    printf("姓名:%s\n", accounts[index].name);
    printf("余额:%.2f\n", accounts[index].balance);
}

void viewTransactions() {
    char accountNumber[20], password[20];
    
    printf("\n=== 交易记录 ===\n");
    printf("请输入账号:");
    scanf("%s", accountNumber);
    printf("请输入密码:");
    scanf("%s", password);
    
    int index = findAccount(accountNumber, password);
    if (index == -1) {
        printf("账号或密码错误!\n");
        return;
    }
    
    if (accounts[index].transactionCount == 0) {
        printf("暂无交易记录!\n");
        return;
    }
    
    printf("\n%-20s %-10s %-10s %-20s\n", "时间", "类型", "金额", "说明");
    printf("--------------------------------------------------------------\n");
    
    for (int i = 0; i < accounts[index].transactionCount; i++) {
        Transaction* t = &accounts[index].transactions[i];
        struct tm* tm_info = localtime(&t->timestamp);
        char timeStr[20];
        strftime(timeStr, 20, "%Y-%m-%d %H:%M:%S", tm_info);
        
        printf("%-20s %-10s %-10.2f %-20s\n", 
               timeStr, t->type, t->amount, t->description);
    }
}

void saveData() {
    FILE* fp = fopen("bank.dat", "wb");
    if (fp == NULL) {
        printf("无法保存数据!\n");
        return;
    }
    
    // 写入账户数量
    fwrite(&accountCount, sizeof(int), 1, fp);
    
    // 写入所有账户数据
    fwrite(accounts, sizeof(Account), accountCount, fp);
    
    fclose(fp);
    printf("数据已保存!\n");
}

void loadData() {
    FILE* fp = fopen("bank.dat", "rb");
    if (fp == NULL) {
        printf("未找到数据文件,将创建新系统。\n");
        return;
    }
    
    // 读取账户数量
    fread(&accountCount, sizeof(int), 1, fp);
    
    // 读取所有账户数据
    fread(accounts, sizeof(Account), accountCount, fp);
    
    fclose(fp);
    printf("已加载 %d 个账户数据。\n", accountCount);
}

void showMainMenu() {
    printf("\n========== 银行账户管理系统 ==========\n");
    printf("1. 开户\n");
    printf("2. 存款\n");
    printf("3. 取款\n");
    printf("4. 转账\n");
    printf("5. 查询余额\n");
    printf("6. 交易记录\n");
    printf("7. 保存数据\n");
    printf("0. 退出\n");
    printf("=====================================\n");
}

项目解析

  • 模块化设计:每个功能独立函数,职责清晰
  • 数据持久化:使用二进制文件保存/加载数据
  • 时间戳:使用time()函数记录交易时间
  • 错误处理:密码验证、余额检查、金额验证
  • 循环菜单:持续交互直到用户退出
  • 数据完整性:转账时同时修改两个账户,保证原子性

四、课程考核标准详解

4.1 考核方式与权重分配

考核项目 权重 考核内容 评价标准
平时成绩 30% 出勤、课堂表现、作业 出勤率≥80%,作业独立完成
实验成绩 30% 8次实验报告 代码规范、功能完整、有创新点
期末考试 40% 笔试(闭卷) 理论知识+代码阅读+编程题

4.2 实验评分细则(满分100分)

实验报告要求

  1. 实验目的(5分):清晰说明实验目标
  2. 实验原理(10分):相关知识点的正确理解
  3. 实验代码(40分):
    • 代码规范(10分):命名规范、缩进统一、注释完整
    • 功能正确(20分):所有功能点实现正确
    • 边界处理(10分):异常输入、边界条件处理
  4. 运行结果(20分):截图清晰、测试用例完整
  5. 实验总结(15分):心得体会、遇到的问题及解决方法
  6. 创新加分(10分):功能扩展、界面优化、算法改进

代码规范具体要求

// ✅ 良好示例
#include <stdio.h>
#define MAX_SIZE 100

// 函数命名:小驼峰,动词开头
void calculateStudentAverage(int scores[], int n) {
    // 变量命名:有意义的英文单词
    int totalScore = 0;
    float averageScore = 0.0;
    
    // 注释:解释复杂逻辑
    // 计算总分,注意防止溢出
    for (int i = 0; i < n; i++) {
        totalScore += scores[i];
    }
    
    averageScore = (float)totalScore / n;
    printf("平均分:%.2f\n", averageScore);
}

// ❌ 不良示例
void func(int a[], int b) {  // 函数名无意义
    int c = 0;               // 变量名无意义
    float d = 0;
    for (int i = 0; i < b; i++) {
        c += a[i];           // 无注释
    }
    d = c / b;               // 整数除法错误
    printf("%f\n", d);       // 输出格式不明确
}

4.3 期末考试题型与评分标准

题型分布

  1. 选择题(20分):基础语法、概念理解
  2. 填空题(15分):代码补全、输出预测
  3. 程序阅读题(20分):代码分析、输出结果
  4. 程序设计题(30分):算法实现、函数设计
  5. 综合应用题(15分):项目设计、系统分析

评分标准

  • 基础题:答案正确即得分
  • 编程题
    • 语法正确(30%)
    • 逻辑正确(40%)
    • 边界处理(20%)
    • 代码规范(10%)
  • 综合题:按步骤给分,关键算法正确即可得分

4.4 项目实战考核要求

项目选题范围

  • 学生信息管理系统
  • 图书管理系统
  • 银行账户系统
  • 简易计算器
  • 文件加密工具
  • 成绩统计分析系统
  • 聊天室(网络编程选修)
  • 简易操作系统工具

项目评分标准(100分)

  1. 功能完整性(30分):需求功能全部实现
  2. 代码质量(25分):
    • 模块化设计(10分)
    • 代码规范(10分)
    • 注释完整(5分)
  3. 创新性(15分):界面优化、功能扩展、算法创新
  4. 文档完整性(15分):需求分析、设计文档、用户手册
  5. 演示与答辩(15分):现场演示、问题回答

项目提交要求

  • 源代码(.c/.h文件)
  • 项目文档(Word/PDF)
  • 可执行程序
  • 演示视频(可选)
  • README说明文件

五、学习建议与资源推荐

5.1 学习路径建议

零基础学生(第1-4周)

  1. 每天保证2小时学习时间
    • 1小时理论学习
    • 1小时动手实践
  2. 重视基础语法
    • 不要急于求成
    • 每个知识点都要亲手敲代码验证
  3. 建立编程思维
    • 画流程图辅助理解
    • 从简单问题开始

进阶阶段(第5-12周)

  1. 理解内存模型
    • 画内存图理解指针
    • 使用调试器观察变量变化
  2. 培养调试能力
    • 学会使用gdb/lldb
    • 掌握printf调试法
  3. 代码复盘
    • 每周回顾本周代码
    • 重构旧代码,优化设计

项目阶段(第13-16周)

  1. 先设计再编码
    • 画出模块关系图
    • 定义清晰的接口
  2. 版本控制
    • 学习使用Git
    • 每天提交代码
  3. 测试驱动
    • 先写测试用例
    • 再实现功能

5.2 常见问题与解决方案

问题1:指针理解困难

症状:指针声明、使用、传参总是出错 解决方案

// 画内存图理解
int a = 10;        // a在栈上,值为10
int* p = &a;       // p在栈上,值为a的地址
                   // *p表示访问该地址的值

// 调试时打印地址和值
printf("&a = %p, a = %d\n", &a, a);
printf("&p = %p, p = %p, *p = %d\n", &p, p, *p);

问题2:数组越界

症状:程序运行结果随机,有时崩溃 解决方案

// 始终检查边界
int arr[10];
for (int i = 0; i < 10; i++) {  // 注意是<10,不是<=10
    arr[i] = i;
}

// 使用安全的输入函数
char str[10];
fgets(str, sizeof(str), stdin);  // 限制输入长度

问题3:内存泄漏

症状:程序运行时间长后变慢或崩溃 解决方案

// malloc和free必须配对
int* p = (int*)malloc(100 * sizeof(int));
if (p == NULL) {
    // 处理分配失败
    return;
}

// 使用p...

free(p);  // 释放内存
p = NULL; // 防止悬空指针

5.3 推荐学习资源

在线教程

  • C语言中文网:c.biancheng.net
  • 菜鸟教程:www.runoob.com/cprogramming
  • cplusplus.com:英文标准参考

书籍推荐

  • 入门:《C Primer Plus》(第6版)
  • 进阶:《C陷阱与缺陷》、《C专家编程》
  • 经典:《The C Programming Language》(K&R)

开发工具

  • IDE:Visual Studio(Windows)、CLion(跨平台)
  • 编辑器:VS Code + C/C++插件
  • 编译器:GCC(Linux)、Clang(macOS)
  • 调试器:GDB、LLDB

在线练习平台

  • LeetCode:算法练习
  • 牛客网:企业真题
  • PTA:高校程序设计实验平台

六、教学大纲执行说明

6.1 教师教学建议

  • 理论与实践比例:1:1,每节课至少留30分钟动手编程
  • 作业布置:每周1次编程作业,难度递进
  • 答疑时间:每周固定2小时线下答疑+线上随时答疑
  • 分组教学:项目阶段2-3人一组,培养协作能力

6.2 学生考核预警

  • 出勤率低于60%:取消考试资格
  • 实验缺交3次以上:实验成绩按0分计
  • 期末考试缺考:直接重修
  • 项目抄袭:本课程成绩记0分,并给予纪律处分

6.3 课程调整机制

  • 根据学生反馈:每4周收集一次教学反馈
  • 根据学习进度:动态调整教学节奏
  • 根据技术发展:适时更新实验内容

七、总结

C语言程序设计课程是理工科学生计算机能力培养的基石。本教学大纲通过16周系统学习,从零基础开始,逐步深入,最终达到项目实战水平。课程强调理论与实践并重,通过丰富的实验和项目,培养学生的编程思维和工程能力。

成功的关键

  1. 坚持动手实践:编程是技能,不是理论
  2. 重视调试过程:错误是最好的老师
  3. 培养规范意识:良好的习惯受益终身
  4. 勇于创新尝试:在项目中展现个人特色

希望每位同学都能通过本课程的学习,不仅掌握C语言这门工具,更重要的是培养出严谨的逻辑思维解决实际问题的能力,为未来的专业学习和职业发展打下坚实基础!


附录:课程资源二维码 (此处可放置课程网站、实验平台、答疑群等二维码)

版本信息

  • 本大纲版本:v2.0
  • 更新日期:2024年1月
  • 适用学期:2024春季学期
  • 编写教师:计算机学院程序设计教学团队