C语言实战|电影票房管理系统(完整可运行版)|逐行解析+新手友好


作为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语言支持结构体整体赋值。

三、运行效果说明

  1. 编译运行:使用gcc 文件名.c -o 电影票房系统编译,执行./电影票房系统(Windows执行电影票房系统.exe);
  2. 核心功能测试:
    • 选择5:显示所有电影,包含ID、标题、导演、成本、票房、利润率;
    • 选择7:按投资回报率排序,标注盈利/亏损状态;
    • 选择0:退出系统,自动释放内存。

五、拓展建议(可选)

当前代码预留了“添加/删除/修改/搜索”等功能接口,新手可补充实现:

  1. 添加电影:判断数组是否满,满则用realloc扩容;
  2. 删除电影:按ID查找,后续元素前移,数量-1;
  3. 修改电影:按ID查找,更新指定字段;
  4. 搜索电影:按标题/导演模糊匹配。

总结

  1. 该系统核心是基于动态数组+结构体实现电影数据的内存管理,malloc/free保证内存高效利用且无泄漏;
  2. 安全输入函数解决了scanf读取非数字崩溃的问题,冒泡排序优化(flag标记)提升了排序效率;
  3. 代码采用“功能封装”思想,每个函数只负责单一功能,符合C语言编程的最佳实践。

江苏省2025年高校专项计划部分高校分数线与报考分析

我的Python第二周作业|列表操作+数值计算实战,基础语法全掌握

评 论