引言:C语言在理工科教育中的核心地位
C语言作为计算机科学与技术领域的基石性编程语言,长期以来在理工学院的教学体系中占据着不可替代的重要地位。它不仅是计算机专业学生入门的首选语言,更是电子工程、自动化、机械工程等众多理工科专业学生必须掌握的核心技能。本教学大纲旨在为理工学院学生提供一套从零基础开始,循序渐进,最终达到能够独立完成中等规模项目实战能力的完整学习路径。
一、课程基本信息与定位
1.1 课程名称与代码
- 课程名称:C语言程序设计
- 课程代码:CS101(计算机类)/ EE101(电子工程类)
- 课程性质:专业基础必修课
- 学时分配:理论教学48学时 + 实验教学32学时 = 总计80学时
- 学分:5学分
1.2 先修课程与依赖关系
本课程假设学生具备以下基础:
- 高中数学知识(特别是逻辑思维和代数运算)
- 基本的计算机操作能力(Windows/Linux操作系统使用)
- 无需编程经验,从零开始教学
1.3 适用专业范围
- 计算机科学与技术
- 软件工程
- 电子信息工程
- 自动化
- 机械电子工程
- 物联网工程
- 生物医学工程
二、课程目标体系详解
2.1 总体目标
通过本课程的学习,使学生能够:
- 掌握C语言的基本语法和编程规范
- 理解程序设计的基本思想和算法逻辑
- 具备独立分析和解决中等复杂度问题的能力
- 能够使用C语言开发小型应用程序和系统工具
- 为后续学习数据结构、操作系统、嵌入式系统等课程打下坚实基础
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/scanffwrite/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分)
实验报告要求
- 实验目的(5分):清晰说明实验目标
- 实验原理(10分):相关知识点的正确理解
- 实验代码(40分):
- 代码规范(10分):命名规范、缩进统一、注释完整
- 功能正确(20分):所有功能点实现正确
- 边界处理(10分):异常输入、边界条件处理
- 运行结果(20分):截图清晰、测试用例完整
- 实验总结(15分):心得体会、遇到的问题及解决方法
- 创新加分(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 期末考试题型与评分标准
题型分布
- 选择题(20分):基础语法、概念理解
- 填空题(15分):代码补全、输出预测
- 程序阅读题(20分):代码分析、输出结果
- 程序设计题(30分):算法实现、函数设计
- 综合应用题(15分):项目设计、系统分析
评分标准
- 基础题:答案正确即得分
- 编程题:
- 语法正确(30%)
- 逻辑正确(40%)
- 边界处理(20%)
- 代码规范(10%)
- 综合题:按步骤给分,关键算法正确即可得分
4.4 项目实战考核要求
项目选题范围
- 学生信息管理系统
- 图书管理系统
- 银行账户系统
- 简易计算器
- 文件加密工具
- 成绩统计分析系统
- 聊天室(网络编程选修)
- 简易操作系统工具
项目评分标准(100分)
- 功能完整性(30分):需求功能全部实现
- 代码质量(25分):
- 模块化设计(10分)
- 代码规范(10分)
- 注释完整(5分)
- 创新性(15分):界面优化、功能扩展、算法创新
- 文档完整性(15分):需求分析、设计文档、用户手册
- 演示与答辩(15分):现场演示、问题回答
项目提交要求
- 源代码(.c/.h文件)
- 项目文档(Word/PDF)
- 可执行程序
- 演示视频(可选)
- README说明文件
五、学习建议与资源推荐
5.1 学习路径建议
零基础学生(第1-4周)
- 每天保证2小时学习时间
- 1小时理论学习
- 1小时动手实践
- 重视基础语法
- 不要急于求成
- 每个知识点都要亲手敲代码验证
- 建立编程思维
- 画流程图辅助理解
- 从简单问题开始
进阶阶段(第5-12周)
- 理解内存模型
- 画内存图理解指针
- 使用调试器观察变量变化
- 培养调试能力
- 学会使用gdb/lldb
- 掌握printf调试法
- 代码复盘
- 每周回顾本周代码
- 重构旧代码,优化设计
项目阶段(第13-16周)
- 先设计再编码
- 画出模块关系图
- 定义清晰的接口
- 版本控制
- 学习使用Git
- 每天提交代码
- 测试驱动
- 先写测试用例
- 再实现功能
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周系统学习,从零基础开始,逐步深入,最终达到项目实战水平。课程强调理论与实践并重,通过丰富的实验和项目,培养学生的编程思维和工程能力。
成功的关键:
- 坚持动手实践:编程是技能,不是理论
- 重视调试过程:错误是最好的老师
- 培养规范意识:良好的习惯受益终身
- 勇于创新尝试:在项目中展现个人特色
希望每位同学都能通过本课程的学习,不仅掌握C语言这门工具,更重要的是培养出严谨的逻辑思维和解决实际问题的能力,为未来的专业学习和职业发展打下坚实基础!
附录:课程资源二维码 (此处可放置课程网站、实验平台、答疑群等二维码)
版本信息:
- 本大纲版本:v2.0
- 更新日期:2024年1月
- 适用学期:2024春季学期
- 编写教师:计算机学院程序设计教学团队
