作为C语言课程设计的经典练手项目,这个电影票房管理系统覆盖了结构体、动态内存分配、动态数组、冒泡排序、安全输入等核心知识点,是从C语言新手过渡到中级的绝佳案例。本文包含完整可直接运行的代码、逐行详细解释,零基础也能跟着学懂。
一、完整可运行代码
//StudybarCommentBegin
/*使用动态内存分配*/
/*电影库使用动态数组*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <time.h>
#define INITIAL_CAPACITY 20 // 初始容量
#define MAX_NAME_LEN 50 // 电影名称最大长度
#define MAX_DIRECTOR_LEN 40 // 导演姓名最大长度
#define DATE_LEN 11 // 日期长度(YYYY-MM-DD)
#define MAX_RESULTS 50 // 最大搜索结果数量
#define GROWTH_FACTOR 2 // 扩容因子
// 电影结构体定义
typedef struct {
int id; // 电影唯一ID
char title[MAX_NAME_LEN]; // 电影标题
char director[MAX_DIRECTOR_LEN]; // 导演姓名
char release_date[DATE_LEN]; // 上映日期
float production_cost; // 制作成本(万元)
float box_office; // 票房收入(万元)
} Movie;
// 数据库结构体(动态数组)
typedef struct {
Movie* movies; // 指向电影数组的指针
int movie_count; // 当前电影数量
int capacity; // 当前容量
} MovieDatabase;
// 函数声明
int init_database(MovieDatabase* db);//前缀代码中已定义
void free_database(MovieDatabase* db);//前缀代码中已定义
int get_int_input(const char* prompt);//在第17题代码基础上修改
void display_all(const MovieDatabase* db);//在第17题代码基础上修改
double calculate_profit_rate(const Movie* m);//利润率,在第17题代码基础上修改
void sort_movies(const MovieDatabase* db);//在第14题代码基础上修改
float calculate_roi(const Movie* m);//投资回报率,在第14题代码基础上修改
// 菜单函数
void display_main_menu();//在第17题代码基础上修改
void handle_user_choice(MovieDatabase* db, int choice);//在第17题代码基础上修改
int main() {
MovieDatabase db;
// 初始化数据库
if (!init_database(&db)) {
printf("数据库初始化失败!\n");
return 1;
}
printf("电影票房管理系统启动成功!\n");
int choice;
do {
display_main_menu();
choice = get_int_input("请选择操作");
handle_user_choice(&db, choice);
} while (choice != 0);
// 释放内存
free_database(&db);
printf("感谢使用电影票房管理系统!\n");
return 0;
}
int init_database(MovieDatabase* db) {
db->movies = (Movie*)malloc(INITIAL_CAPACITY * sizeof(Movie));
if (db->movies == NULL) {
printf("内存分配失败!\n");
return 0;
}
db->movie_count = 0;
db->capacity = INITIAL_CAPACITY;
// 添加示例数据
Movie sample_movies[] = {
{1, "流浪地球", "郭帆", "2023-01-22", 50000, 465000},
{2, "满江红", "张艺谋", "2023-01-22", 20000, 453000},
{3, "消失的她", "崔睿", "2023-06-22", 15000, 353000},
{4, "封神第一部", "乌尔善", "2023-07-20", 80000, 264000},
{5, "八角笼中", "王宝强", "2023-07-06", 10000, 2200},
{6, "长安三万里", "谢君伟", "2023-07-08", 30000, 183000},
{7, "熊出没", "林永长", "2023-01-22", 8000, 150000},
{8, "坚如磐石", "张艺谋", "2023-09-28", 25000, 1330},
{9, "人生路不熟", "易小星", "2023-04-28", 15000, 118000},
{10, "毒舌律师", "吴炜伦", "2023-02-24", 5000, 105000}
};
int sample_count = sizeof(sample_movies) / sizeof(sample_movies[0]);
for (int i = 0; i < sample_count; i++) {
db->movies[db->movie_count] = sample_movies[i];
db->movie_count++;
}
printf("数据库已初始化,包含 %d 部电影,容量:%d\n",
db->movie_count, db->capacity);
return 1;
}
// 释放数据库内存
void free_database(MovieDatabase* db) {
if (db->movies != NULL) {
free(db->movies);
db->movies = NULL;
}
db->movie_count = 0;
db->capacity = 0;
printf("数据库内存已释放\n");
}
void display_main_menu()
{
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("8. 计算利润率\n");
printf("9. 生成统计报告\n");
printf("10. 票房模拟预测\n");
printf("11. 电影推荐\n");
printf("0. 退出系统\n");
printf("══════════════════════════════════════════\n");
return;
}
int get_int_input(const char* prompt)
{
printf("%s:", prompt);
int a;
while (1)
if (scanf("%d", &a) == 1)
{
while (getchar() != '\n' && getchar() != EOF);
return a;
}
else
{
while (getchar() != '\n' && getchar() != EOF);
printf("输入无效,请重新输入\n");
}
return a;
}
//处理用户选择
void handle_user_choice(MovieDatabase* db, int choice)
{
switch (choice)
{
case 1:printf("添加新电影\n");break;
case 2:printf("删除电影\n");break;
case 3:printf("修改电影信息\n");break;
case 4:printf("搜索电影\n");break;
case 5:display_all(db);break;
case 6:printf("票房排行榜\n");break;
case 7:sort_movies(db);break;
case 8:printf("计算利润率\n");break;
case 9:printf("生成统计报告\n");break;
case 10:printf("票房模拟预测\n");break;
case 11:printf("电影推荐\n");break;
case 0:printf("正在退出系统...\n");break;
default:printf("无效选择!\n");break;
}
return;
}
void display_all(const MovieDatabase* db)
{
if (db->movie_count == 0) {
printf("没有电影记录!\n");
return;
}
printf("\n\n════════════ 所有电影列表 (%d部) ════════════\n", db->movie_count);
printf("%-4s%-20s%-12s%-12s%-10s%-10s%-8s\n",
"ID", "标题", "导演", "上映日期", "成本(万)", "票房(万)", "利润率");
printf("────────────────────────────────────────────────────────\n");
for (int i = 0; i < db->movie_count; i++) {
Movie* s = &db->movies[i];
double rate = calculate_profit_rate(s);
printf("%-4d%-20s%-12s%-12s%-10.2f%-10.2f%-8.2f%%\n",
s->id, s->title, s->director, s->release_date, s->production_cost, s->box_office, rate);
}
printf("────────────────────────────────────────────────────────\n");
return;
}
//利润率
double calculate_profit_rate(const Movie* m)
{
return (m->box_office - m->production_cost) / m->production_cost * 100;
}
float calculate_roi(const Movie* m)
{
return m->box_office / m->production_cost;
}
void sort_movies(const MovieDatabase* db)
{
for (int i = 0;i < db->movie_count - 1;i++)
{
int flag = 1;
for (int j = 0;j < db->movie_count - i - 1;j++)
if (calculate_roi(&db->movies[j]) < calculate_roi(&db->movies[j + 1]))
{
Movie temp = db->movies[j];
db->movies[j] = db->movies[j + 1];
db->movies[j + 1] = temp;
flag = 0;
}
if (flag)
break;
}
printf("===== 按投资回报率从高到低排序 =====\n");
printf("%-5s%-5s%-20s%-10s%-10s\n", "排名", "ID", "标题", "投资回报率", "状态");
for (int i = 0; i < db->movie_count;i++)
{
if (calculate_roi(&db->movies[i]) > 1)
printf("%-5d%-5d%-20s%-10.2f%-10s\n", i + 1, db->movies[i].id, db->movies[i].title, calculate_roi(&db->movies[i]), "盈利");
else
printf("%-5d%-5d%-20s%-10.2f%-10s\n", i + 1, db->movies[i].id, db->movies[i].title, calculate_roi(&db->movies[i]), "亏损");
}
}
//StudybarCommentEnd
二、核心代码逐行解析(新手友好版)
1. 头文件与宏定义:基础配置
#include <stdio.h> // 提供printf/scanf等输入输出函数
#include <stdlib.h> // 提供malloc/free等动态内存管理函数
#include <string.h> // 提供字符串操作函数(本代码暂未用到,预留)
#include <ctype.h> // 提供字符处理函数(本代码暂未用到,预留)
#include <time.h> // 提供时间相关函数(本代码暂未用到,预留)
#define INITIAL_CAPACITY 20 // 动态数组初始容量:默认存20部电影
#define MAX_NAME_LEN 50 // 电影名称最大长度,防止字符溢出
#define MAX_DIRECTOR_LEN 40 // 导演姓名最大长度
#define DATE_LEN 11 // 日期格式"YYYY-MM-DD"占11个字符(含结束符)
#define MAX_RESULTS 50 // 预留:搜索结果最大数量
#define GROWTH_FACTOR 2 // 预留:数组满了后扩容2倍
关键说明:#define 是宏定义,相当于“常量别名”,修改时只需改一处,提升代码可维护性。
2. 结构体定义:自定义数据类型
(1)电影结构体 Movie
typedef struct {
int id; // 电影唯一ID,用于标识不同电影
char title[MAX_NAME_LEN]; // 电影标题(字符数组存储)
char director[MAX_DIRECTOR_LEN]; // 导演姓名
char release_date[DATE_LEN]; // 上映日期
float production_cost; // 制作成本(浮点型,支持小数)
float box_office; // 票房收入(万元)
} Movie;
关键说明:
struct是C语言自定义结构体的关键字,用于封装一组相关数据;typedef给结构体起别名Movie,后续可直接用Movie定义变量,无需写struct Movie;- 字符数组(如
title)用于存储字符串,长度需提前定义防止溢出。
(2)数据库结构体 MovieDatabase
typedef struct {
Movie* movies; // 指向电影数组的指针(动态数组核心)
int movie_count; // 当前已存储的电影数量
int capacity; // 数组最大容量(可扩容)
} MovieDatabase;
关键说明:
Movie* movies是动态数组的核心:通过指针指向malloc分配的内存区域;movie_count记录实际数据量,capacity记录数组总容量,两者分离实现“动态扩容”。
3. 函数声明:提前告知编译器函数存在
int init_database(MovieDatabase* db);// 初始化数据库
void free_database(MovieDatabase* db);// 释放内存
int get_int_input(const char* prompt);// 安全读取整数输入
void display_all(const MovieDatabase* db);// 显示所有电影
double calculate_profit_rate(const Movie* m);// 计算利润率
void sort_movies(const MovieDatabase* db);// 按投资回报率排序
float calculate_roi(const Movie* m);// 计算投资回报率
void display_main_menu();// 显示主菜单
void handle_user_choice(MovieDatabase* db, int choice);// 处理用户菜单选择
关键说明:C语言要求“先声明后使用”,函数定义写在main之后时,需提前声明函数名、返回值、参数类型。
4. 主函数 main:程序入口
int main() {
MovieDatabase db; // 定义数据库变量
// 初始化数据库:分配内存+添加示例数据
if (!init_database(&db)) { // 初始化失败则退出程序
printf("数据库初始化失败!\n");
return 1;
}
printf("电影票房管理系统启动成功!\n");
int choice;
do { // 循环显示菜单,直到用户选择0退出
display_main_menu(); // 显示菜单
choice = get_int_input("请选择操作"); // 读取用户选择
handle_user_choice(&db, choice); // 处理选择
} while (choice != 0);
// 退出前释放动态内存,避免内存泄漏
free_database(&db);
printf("感谢使用电影票房管理系统!\n");
return 0; // 程序正常退出
}
关键说明:
do-while循环:先执行一次菜单显示,再判断是否退出,保证至少显示一次菜单;&db传递结构体地址:函数需修改结构体内容时,必须传指针(值传递无法修改原变量);return 1表示程序异常退出,return 0表示正常退出。
5. 数据库初始化 init_database:分配内存+填充示例数据
int init_database(MovieDatabase* db) {
// 分配初始内存:容量20 * 每个Movie结构体的大小
db->movies = (Movie*)malloc(INITIAL_CAPACITY * sizeof(Movie));
if (db->movies == NULL) { // 内存分配失败返回0
printf("内存分配失败!\n");
return 0;
}
db->movie_count = 0; // 初始电影数量为0
db->capacity = INITIAL_CAPACITY; // 初始容量20
// 定义示例电影数据
Movie sample_movies[] = {
{1, "流浪地球", "郭帆", "2023-01-22", 50000, 465000},
{2, "满江红", "张艺谋", "2023-01-22", 20000, 453000},
{3, "消失的她", "崔睿", "2023-06-22", 15000, 353000},
{4, "封神第一部", "乌尔善", "2023-07-20", 80000, 264000},
{5, "八角笼中", "王宝强", "2023-07-06", 10000, 2200},
{6, "长安三万里", "谢君伟", "2023-07-08", 30000, 183000},
{7, "熊出没", "林永长", "2023-01-22", 8000, 150000},
{8, "坚如磐石", "张艺谋", "2023-09-28", 25000, 1330},
{9, "人生路不熟", "易小星", "2023-04-28", 15000, 118000},
{10, "毒舌律师", "吴炜伦", "2023-02-24", 5000, 105000}
};
// 计算示例数据的数量:总字节数 / 单个Movie的字节数
int sample_count = sizeof(sample_movies) / sizeof(sample_movies[0]);
// 循环将示例数据存入动态数组
for (int i = 0; i < sample_count; i++) {
db->movies[db->movie_count] = sample_movies[i];
db->movie_count++; // 每存一部,数量+1
}
printf("数据库已初始化,包含 %d 部电影,容量:%d\n",
db->movie_count, db->capacity);
return 1; // 初始化成功返回1
}
关键说明:
malloc函数:向系统申请指定大小的内存,返回void指针,需强制转换为Movie*;sizeof(Movie):计算单个电影结构体的字节大小,保证内存分配准确;- 内存分配失败时
malloc返回NULL,必须判断,否则会导致程序崩溃。
6. 内存释放 free_database:避免内存泄漏
void free_database(MovieDatabase* db) {
if (db->movies != NULL) { // 确保指针非空再释放
free(db->movies); // 释放动态分配的内存
db->movies = NULL; // 指针置空,避免野指针
}
db->movie_count = 0; // 重置数量
db->capacity = 0; // 重置容量
printf("数据库内存已释放\n");
}
关键说明:
free仅释放内存,不会自动置空指针,需手动将db->movies设为NULL,防止“野指针”;- 内存泄漏:程序结束前未释放
malloc分配的内存,会导致系统资源浪费,长期运行可能卡顿。
7. 菜单显示 display_main_menu:交互界面
void display_main_menu()
{
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("8. 计算利润率\n");
printf("9. 生成统计报告\n");
printf("10. 票房模拟预测\n");
printf("11. 电影推荐\n");
printf("0. 退出系统\n");
printf("══════════════════════════════════════════\n");
return;
}
关键说明:纯格式化输出,提升用户交互体验,return可省略(无返回值函数)。
8. 安全输入 get_int_input:防止输入非数字崩溃
int get_int_input(const char* prompt)
{
printf("%s:", prompt); // 提示用户输入
int a;
while (1) // 无限循环,直到输入有效
if (scanf("%d", &a) == 1) // 成功读取整数(返回1)
{
// 清空输入缓冲区,避免残留字符影响后续输入
while (getchar() != '\n' && getchar() != EOF);
return a; // 返回有效整数
}
else
{
// 清空错误输入(非数字)
while (getchar() != '\n' && getchar() != EOF);
printf("输入无效,请重新输入\n"); // 提示错误
}
return a; // 逻辑上不会执行,仅满足语法要求
}
关键说明:
scanf("%d", &a):成功读取整数返回1,读取非数字返回0;getchar():逐个读取输入缓冲区的字符,直到换行符,清空无效输入;- 若无此函数,用户输入字母会导致
scanf失败,程序进入死循环或崩溃。
9. 菜单处理 handle_user_choice:分支逻辑
void handle_user_choice(MovieDatabase* db, int choice)
{
switch (choice)
{
case 1:printf("添加新电影\n");break; // 预留功能
case 2:printf("删除电影\n");break; // 预留功能
case 3:printf("修改电影信息\n");break; // 预留功能
case 4:printf("搜索电影\n");break; // 预留功能
case 5:display_all(db);break; // 显示所有电影
case 6:printf("票房排行榜\n");break; // 预留功能
case 7:sort_movies(db);break; // 排序电影
case 8:printf("计算利润率\n");break; // 预留功能
case 9:printf("生成统计报告\n");break; // 预留功能
case 10:printf("票房模拟预测\n");break; // 预留功能
case 11:printf("电影推荐\n");break; // 预留功能
case 0:printf("正在退出系统...\n");break; // 退出
default:printf("无效选择!\n");break; // 输入错误
}
return;
}
关键说明:
switch-case适合处理多分支(菜单选择),比多个if-else更简洁;break防止“分支穿透”:执行完当前case后跳出switch,否则会继续执行下一个case。
10. 显示所有电影 display_all:格式化输出
void display_all(const MovieDatabase* db)
{
if (db->movie_count == 0) { // 无数据时提示
printf("没有电影记录!\n");
return;
}
// 标题栏:%-4s 表示左对齐,占4个字符宽度
printf("\n\n════════════ 所有电影列表 (%d部) ════════════\n", db->movie_count);
printf("%-4s%-20s%-12s%-12s%-10s%-10s%-8s\n",
"ID", "标题", "导演", "上映日期", "成本(万)", "票房(万)", "利润率");
printf("────────────────────────────────────────────────────────\n");
// 循环遍历所有电影
for (int i = 0; i < db->movie_count; i++) {
Movie* s = &db->movies[i]; // 取当前电影的地址(简化代码)
double rate = calculate_profit_rate(s); // 计算利润率
// 格式化输出:%.2f 保留2位小数,%% 输出百分号
printf("%-4d%-20s%-12s%-12s%-10.2f%-10.2f%-8.2f%%\n",
s->id, s->title, s->director, s->release_date,
s->production_cost, s->box_office, rate);
}
printf("────────────────────────────────────────────────────────\n");
return;
}
关键说明:
%-4d:左对齐,占4个字符宽度的整数,保证输出格式整齐;%.2f:浮点型保留2位小数,符合金额显示习惯;%%:输出百分号(%是格式化符,需转义);Movie* s = &db->movies[i]:减少重复书写db->movies[i],提升代码可读性。
11. 利润率 & 投资回报率计算:业务逻辑
// 利润率 = (票房 - 成本) / 成本 * 100%
double calculate_profit_rate(const Movie* m)
{
return (m->box_office - m->production_cost) / m->production_cost * 100;
}
// 投资回报率 = 票房 / 成本(判断盈利/亏损的核心指标)
float calculate_roi(const Movie* m)
{
return m->box_office / m->production_cost;
}
关键说明:
const Movie* m:指针加const,表示函数内不会修改m指向的内容,提升代码安全性;- 利润率用
double(双精度浮点),回报率用float(单精度),根据精度需求选择。
12. 电影排序 sort_movies:冒泡排序
void sort_movies(const MovieDatabase* db)
{
// 冒泡排序:外层循环控制排序轮数
for (int i = 0;i < db->movie_count - 1;i++)
{
int flag = 1; // 优化:标记是否已有序
// 内层循环:每轮比较相邻元素,将小的往后移
for (int j = 0;j < db->movie_count - i - 1;j++)
if (calculate_roi(&db->movies[j]) < calculate_roi(&db->movies[j + 1]))
{
// 交换两个电影数据
Movie temp = db->movies[j];
db->movies[j] = db->movies[j + 1];
db->movies[j + 1] = temp;
flag = 0; // 有交换,说明未完全有序
}
if (flag) break; // 无交换,提前退出循环
}
// 输出排序结果
printf("===== 按投资回报率从高到低排序 =====\n");
printf("%-5s%-5s%-20s%-10s%-10s\n", "排名", "ID", "标题", "投资回报率", "状态");
for (int i = 0; i < db->movie_count;i++)
{
// 回报率>1表示盈利,否则亏损
if (calculate_roi(&db->movies[i]) > 1)
printf("%-5d%-5d%-20s%-10.2f%-10s\n",
i+1, db->movies[i].id, db->movies[i].title,
calculate_roi(&db->movies[i]), "盈利");
else
printf("%-5d%-5d%-20s%-10.2f%-10s\n",
i+1, db->movies[i].id, db->movies[i].title,
calculate_roi(&db->movies[i]), "亏损");
}
}
关键说明:
- 冒泡排序核心:相邻元素比较,不符合顺序则交换,每轮将最小/最大元素“冒泡”到末尾;
flag优化:某轮无交换说明数组已有序,提前退出,减少循环次数;- 交换结构体:直接用临时变量
temp存储,C语言支持结构体整体赋值。
三、运行效果说明
- 编译运行:使用
gcc 文件名.c -o 电影票房系统编译,执行./电影票房系统(Windows执行电影票房系统.exe); - 核心功能测试:
- 选择5:显示所有电影,包含ID、标题、导演、成本、票房、利润率;
- 选择7:按投资回报率排序,标注盈利/亏损状态;
- 选择0:退出系统,自动释放内存。
五、拓展建议(可选)
当前代码预留了“添加/删除/修改/搜索”等功能接口,新手可补充实现:
- 添加电影:判断数组是否满,满则用
realloc扩容; - 删除电影:按ID查找,后续元素前移,数量-1;
- 修改电影:按ID查找,更新指定字段;
- 搜索电影:按标题/导演模糊匹配。
总结
- 该系统核心是基于动态数组+结构体实现电影数据的内存管理,
malloc/free保证内存高效利用且无泄漏; - 安全输入函数解决了
scanf读取非数字崩溃的问题,冒泡排序优化(flag标记)提升了排序效率; - 代码采用“功能封装”思想,每个函数只负责单一功能,符合C语言编程的最佳实践。