基于C语言的药品管理系统大作业完整项目实现

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目是一个使用C语言开发的药品管理系统,旨在帮助学生巩固C语言基础知识并提升实际编程能力。系统涵盖药品入库、出库、库存查询、销售统计等核心功能,涉及结构体、数组、文件操作、模块化设计和用户交互界面等关键技术。项目采用结构化编程思想,包含主程序、数据定义、文件读写、功能模块和错误处理等多个组件,代码经过测试可运行,适合作为高校C语言课程设计或大作业参考。通过该实践,学生能够深入理解数据结构与程序设计逻辑,提升软件开发的综合能力。

C语言构建药品管理系统:从结构设计到工程化实践

在医院药房、连锁药店甚至社区卫生站,每天都有成百上千次的药品出入库操作。你有没有想过,这些看似简单的“增删改查”背后,其实是一套精密运转的数据系统?而支撑这一切的,很可能就是用C语言编写的一套轻量级药品管理系统。

这门诞生于上世纪70年代的语言,至今仍在嵌入式设备、操作系统和底层工具中占据不可替代的地位。它像一位沉默的老匠人,不追求花哨的语法糖,却能精准掌控每一个字节的内存分配,直接与硬件对话。今天我们要做的,就是跟着这位“老匠人”,亲手打造一个真正可用的药品管理程序——不是玩具demo,而是具备完整业务逻辑、数据持久化和容错机制的实用系统。

准备好了吗?让我们从最基础的代码结构开始,一步步揭开这个系统的神秘面纱👇


程序入口与交互流程的设计艺术

每个C程序都始于 main 函数,但它绝不仅仅是起点那么简单。想象一下,当医生急着开药时,系统却卡在某个菜单跳转上……用户体验的重要性不言而喻。因此,我们的第一课就是如何让 main 成为整个系统的“指挥中心”。

int main() {
    init_system();          // 初始化数据库
    load_all_drugs();       // 启动时自动加载历史数据

    int choice;
    do {
        show_menu();
        choice = get_user_choice();

        switch (choice) {
            case 1: add_new_drug(); break;
            case 2: view_inventory(); break;
            case 3: drug_inbound_flow(); break;
            case 4: drug_outbound_flow(); break;
            case 5: search_and_report(); break;
            case 0: printf("系统已退出。\n"); break;
            default: printf("⚠️  无效选项,请重新选择!\n");
        }

        if (choice != 0) pause_for_user();
    } while (choice != 0);

    save_all_drugs();       // 关闭前保存所有变更
    return 0;
}

看到这段代码,是不是觉得有点眼熟?没错,这就是典型的 主循环+菜单驱动 架构。但别小看这种模式,它之所以经久不衰,正是因为其简单可靠。每一趟循环就像一次呼吸:展示菜单 → 获取输入 → 执行动作 → 暂停反馈 → 继续下一轮。

这里有几个细节值得玩味:

  • 初始化顺序很重要 :必须先调用 init_system() 清空内存状态,再加载文件数据。否则可能出现旧数据残留或重复加载的问题。
  • 用户暂停机制 :每次操作后执行 pause_for_user() ,避免屏幕刷新太快导致信息丢失。“按任意键继续”虽土,但在命令行环境下依然有效。
  • 退出路径明确 :只有选择 0 才会跳出循环,并触发最终的数据保存。其他非法输入只会提示错误并重新显示菜单。

那菜单本身长什么样呢?

void show_menu() {
    printf("\n💊 药品管理系统 v1.2\n");
    printf("----------------------------\n");
    printf("1. 添加新药品\n");
    printf("2. 查看全部库存\n");
    printf("3. 药品入库(采购)\n");
    printf("4. 药品出库(销售)\n");
    printf("5. 查询与统计\n");
    printf("0. 退出系统\n");
    printf("----------------------------\n");
}

注意到那个小小的 💊 表情了吗?别笑!在纯文本界面里,适当的视觉符号反而能提升可读性。毕竟,谁不喜欢带点温度的技术产品呢?😄


结构体建模:为每盒药建立数字档案

如果说变量是砖块,函数是梁柱,那么结构体就是整栋建筑的框架。在药品系统中,我们需要为每一种药品建立一份完整的电子档案。这份档案不仅要包含基本信息,还得考虑未来的扩展性和性能表现。

如何定义一个“合理”的结构体?

我们先来看最初的版本:

#define MAX_NAME_LEN 50
#define MAX_SPEC_LEN 20

typedef struct {
    int id;                     
    char name[MAX_NAME_LEN];    
    char specification[MAX_SPEC_LEN]; 
    float price;                
    int stock;                  
} Medicine;

看起来很直观对吧?但问题是:为什么 id int 而不是 short long ?为什么不把字符串改成指针动态分配?这些选择都不是随意定的,而是基于实际场景的权衡。

字段类型的选择哲学
成员 类型 决策依据
id int 支持最多约20亿种药品,远超现实需求;4字节约省空间
name char[50] 国内常见药品名不超过30汉字(60字节),50英文字符绰绰有余
specification char[20] “10mg×12片/盒”这类描述通常<15字符,20已留足余量
price float 保留两位小数足够, float 提供6-7位有效数字
stock int 虽然库存非负,但预留负值空间便于退货、调拨等操作

特别是价格字段,有人可能会问:“金融系统都用定点数了,这里为啥敢用浮点?” 其实很简单——药品单价极少超过千元,两位小数精度完全够用。真要担心精度问题,可以用 int 存“分”而不是“元”,比如 1850 表示18.50元。

至于 stock 用有符号整数,这是为了未来可能的业务扩展。比如某天要做库存盘点,发现实物比记录少了5盒,就可以录入 -5 来修正差异。如果强行用 unsigned ,这种操作就得绕弯子。

编译器悄悄加上的“隐形成员”:字节对齐

你以为上面这个结构体只占 4+50+20+4+4=82 字节?太天真了 😏

现代CPU为了提高访问速度,默认会对结构体进行 内存对齐 。也就是说,某些类型的变量必须放在特定地址上,比如 int float 需要4字节对齐(地址能被4整除)。为此,编译器会在成员之间插入填充字节(padding)。

让我们画个内存布局图看看真相:

graph LR
    subgraph Memory Layout of 'Medicine' Struct
        A["Offset 0-3: id (int, 4B)"] 
        B["Offset 4-53: name[50]"]
        C["Offset 54-73: specification[20]"]
        D["Offset 74-75: ⚠️ padding (2B)"]
        E["Offset 76-79: price (float, 4B)"]
        F["Offset 80-83: stock (int, 4B)"]
    end

看到了吗?因为 price 必须从4的倍数地址开始,而前面 specification 结束在offset 73,下一个位置74不符合要求,于是编译器自动插入了2字节的padding!

最终结果是什么? 实际占用84字节,浪费了2字节 。听起来不多?但如果系统要管理1万种药品,那就是整整 20KB 的额外开销!

怎么解决这个问题?答案是调整成员顺序,把对齐要求高的放在一起:

typedef struct {
    int id;         
    float price;    
    int stock;      
    char specification[MAX_SPEC_LEN];
    char name[MAX_NAME_LEN];    
} MedicineOptimized;

现在再看内存分布:

  • id , price , stock 连续存放(0~11),无padding;
  • specification 起始12(对齐要求1,无需pad)→ 占12~31;
  • name 起始32 → 占32~81;
  • 总大小仍为84字节?等等……

咦,好像没变?这是因为即使内部没有padding,结构体整体也会对其边界对齐。在这个例子中,由于最大对齐单位是4字节,所以总大小会向上取整到4的倍数。

真正的优化在于 减少碎片化 。虽然当前案例改善有限,但在更复杂的结构体中(如含 double long long ),排序带来的收益非常可观。

🛠 小贴士:可以用 offsetof(Medicine, field) 宏来查看每个字段的实际偏移位置,验证你的对齐假设。


数据存储策略:静态数组 vs 动态模拟

接下来是个关键抉择:我们该怎么存放这些药品数据?最容易想到的就是静态数组:

#define MAX_MEDICINES 1000
Medicine medicines[MAX_MEDICINES];
int medicine_count = 0;

简单粗暴,但也藏着两大隐患:

  1. 内存浪费严重 :假如只用了50种药品,剩下的950个结构体照样占着84×950≈80KB内存!对于嵌入式设备来说,这简直是奢侈。
  2. 容量硬上限 :一旦超过1000种,就得改宏重新编译。上线后扩容多麻烦?

但你说用 malloc 动态分配?也不是不行,不过要考虑几个现实问题:
- 嵌入式平台可能没有完善的堆管理;
- 频繁 malloc/free 可能导致内存碎片;
- 初学者容易写出内存泄漏代码。

那有没有折中方案?当然有!我们可以用“计数器+手动迁移”的方式 模拟动态行为

void add_medicine(Medicine* db, int* count, const Medicine* new_med) {
    if (*count >= MAX_MEDICINES) {
        printf("❌ 数据库已满!无法添加新药品。\n");
        return;
    }
    db[*count] = *new_med;
    (*count)++;
}

重点来了:第5行的 db[*count] = *new_med; 实际完成了什么操作?

由于 Medicine 结构体不含指针,这是一个 深拷贝 过程!整个84字节的内容都会被复制过去。相比链表节点一个个 malloc ,这种方式更快也更安全。

删除操作稍微复杂些,需要前移覆盖:

int remove_medicine_by_id(Medicine* db, int* count, int target_id) {
    for (int i = 0; i < *count; i++) {
        if (db[i].id == target_id) {
            // 从当前位置向前移动后续元素
            memmove(&db[i], &db[i+1], (*count - i - 1) * sizeof(Medicine));
            (*count)--;
            return 1;
        }
    }
    return 0;
}

注意这里用了 memmove 而不是 memcpy —— 当源和目标区域重叠时, memmove 能保证正确处理,防止数据错乱。

时间复杂度方面,查找O(n),删除O(n),总体还是O(n)。但对于小于1000条记录的小型系统来说,现代CPU几微秒就能搞定,完全不影响体验。

💡 工程经验:读多写少用数组,频繁增删考虑链表;内存紧张就用紧凑结构体+排序+二分查找。


文件持久化:让数据穿越重启

想象一下,辛辛苦苦录了一百种药品,关机后再开机全没了……这谁能受得了?所以我们必须实现 数据落地 ,也就是常说的“存盘”。

但问题来了:C语言不像Python有pickle,Java有序列化,怎么办?只能自己动手,丰衣足食!

为什么选CSV而不是二进制?

你可能会想,直接 fwrite(medicines, sizeof(Medicine), count, fp); 不就行了吗?确实可以,但存在几个致命缺点:

  • 跨平台兼容性差 :不同机器的字节序、对齐方式不同,可能导致数据错乱;
  • 人类不可读 :出了bug没法直接编辑修复;
  • 难以调试 :你想看看某条记录是不是写错了,得用hexdump工具翻半天。

所以我们选择 文本格式 + CSV协议

1001,阿莫西林胶囊,0.25g*24粒,18.50,150
1002,布洛芬缓释片,0.3g*20片,22.00,87

好处显而易见:
- Excel能打开,老板要看报表也方便;
- 出现异常可以直接用记事本修改;
- 解析逻辑简单, strtok 分割搞定。

当然也有风险:万一药品名叫“复方甘草口服液,儿童型”怎么办?逗号冲突了!解决方案有两个:

  1. 禁止用户输入特殊字符(简单粗暴)
  2. 采用标准CSV规则:用双引号包裹含分隔符的字段,如 "复方甘草口服液,儿童型"

我们推荐前者,毕竟药品名称规范化本身就是良好管理的一部分 😉

写入函数的安全封装

int write_drug_to_file(FILE *fp, const Drug *drug) {
    if (!fp || !drug) return -1;

    int ret = fprintf(fp, "%d,%s,%s,%.2f,%d\n",
                      drug->id,
                      drug->name,
                      drug->spec,
                      drug->price,
                      drug->stock);
    return (ret > 0) ? 0 : -1;
}

这里面有几个魔鬼细节:

  • 空指针检查 :防御式编程第一步,永远不要相信传进来的参数。
  • %.2f 格式化输出 :强制保留两位小数,避免 18.4999999 这种浮点误差影响可读性。
  • 返回值判断 fprintf 成功返回写入字符数,失败返回负值。我们把它抽象成“成功0,失败-1”的统一接口。

读取更要注意缓冲区溢出:

int read_drug_from_file(FILE *fp, Drug *drug) {
    if (!fp || !drug) return -1;

    int result = fscanf(fp, "%d,%49[^,],%29[^,],%f,%d\n",
                        &drug->id,
                        drug->name,
                        drug->spec,
                        &drug->price,
                        &drug->stock);

    return (result == 5) ? 0 : (feof(fp) ? 1 : -1);
}

关键技巧在这里: %49[^,] 是什么意思?

它表示:“读取最多49个 不是逗号 的字符”。这样一来,既防止了缓冲区溢出(name只有50字节),又能准确截断到下一个字段。同理 %29[^,] 用于spec字段。

返回值设计也很讲究:
- 0 : 成功读取一条
- 1 : 到达文件末尾(正常情况)
- -1 : 格式错误或I/O异常

这样调用者可以根据不同返回码做相应处理,而不是笼统地说“读取失败”。

启动加载全流程控制

最后是整个生命周期的闭环:

graph TD
    A[程序启动] --> B{是否存在数据文件?}
    B -- 是 --> C[打开文件]
    B -- 否 --> D[初始化空数据集]
    C --> E[逐行解析药品记录]
    E --> F{是否达到数组上限?}
    F -- 否 --> G[存入内存数组]
    F -- 是 --> H[提示溢出并停止]
    G --> I{是否还有下一行?}
    I -- 是 --> E
    I -- 否 --> J[关闭文件]
    J --> K[进入主菜单]
    K --> L[执行增删改查]
    L --> M[用户选择退出]
    M --> N[打开文件(写)]
    N --> O[遍历内存数据写入]
    O --> P{全部写入成功?}
    P -- 是 --> Q[关闭文件]
    P -- 否 --> R[报错并终止]
    Q --> S[程序结束]

这张流程图体现了什么叫“健壮性设计”:

  • 文件不存在?没关系,当作首次运行;
  • 读取中途出错?至少已有数据还能用;
  • 写入失败?绝不静默忽略,必须提醒用户备份原始数据;
  • 边界检查贯穿始终,绝不越界操作。

正是这些看似繁琐的判断,才让系统能在各种意外情况下优雅地应对,而不是一崩了之。


核心业务逻辑:入库与出库的风控机制

如果说数据结构是骨架,文件IO是血脉,那业务逻辑就是心脏。其中最重要的两件事就是 入库 (采购补货)和 出库 (销售发货)。这两个功能直接影响库存准确性,稍有不慎就会造成账实不符。

入库不只是“加数字”那么简单

你以为入库就是找到药品然后 stock += quantity ?Too young too simple!

真实世界中的入库可能遇到这些问题:
- 同一ID的药品,名字变了(厂家改名)?
- 规格不一样了(包装从12片变24片)?
- 采购价大幅波动(涨价 or 录错)?

所以我们需要一套智能合并策略:

int drug_inbound(int id, const char *name, const char *spec, 
                 float price, int quantity) {
    if (quantity <= 0) {
        printf("❌ 入库数量必须大于0。\n");
        return -1;
    }

    for (int i = 0; i < drug_count; ++i) {
        if (drugs[i].id == id) {
            // 名称或规格不一致 → 警告!
            if (strcmp(drugs[i].name, name) || strcmp(drugs[i].spec, spec)) {
                printf("🚨 药品ID=%d的名称或规格不一致!请核实。\n", id);
                return -1;
            }

            // 价格变动超过5% → 提示确认
            if (fabs(drugs[i].price - price)/drugs[i].price > 0.05) {
                printf("🔔 价格变动超5%%!原价:%.2f 新价:%.2f\n", 
                       drugs[i].price, price);
                return -1;
            }

            drugs[i].stock += quantity;
            printf("✅ '%s' 入库成功,当前库存:%d。\n", name, drugs[i].stock);
            return 0;
        }
    }

    // 新药品注册
    if (drug_count >= MAX_DRUGS) {
        printf("❌ 药品列表已满,无法添加。\n");
        return -1;
    }

    // ... 初始化新记录 ...
    printf("🆕 新药品 '%s' 添加成功,初始库存:%d。\n", name, quantity);
    return 0;
}

看到这里的多重校验了吗?这才是生产级系统的模样:

  • 一致性检查 :同一ID必须对应相同的名称和规格,否则可能是条码贴错;
  • 价格风控 :设定±5%阈值,防止录入错误导致财务偏差;
  • 渐进式反馈 :发现问题立即中断,并给出具体提示,而不是冷冰冰的“失败”。

🎯 设计心得:好的系统不是一味接受输入,而是懂得何时说“不”。

出库更要严防死守

如果说入库还能容忍一些弹性,出库就必须铁面无私——绝对不允许负库存!

int drug_outbound(int id, int quantity) {
    if (quantity <= 0) {
        printf("❌ 出库数量必须大于0。\n");
        return -1;
    }

    for (int i = 0; i < drug_count; ++i) {
        if (drugs[i].id == id) {
            if (drugs[i].stock < quantity) {
                printf("⛔ '%s' 库存不足(现有%d,请求%d)。\n",
                       drugs[i].name, drugs[i].stock, quantity);
                return -1;
            }
            drugs[i].stock -= quantity;
            record_sale(id, quantity);  // 更新销售统计
            printf("📤 出库成功:%s 减少 %d,剩余:%d。\n",
                   drugs[i].name, quantity, drugs[i].stock);
            return 0;
        }
    }
    printf("❌ 未找到ID为 %d 的药品。\n", id);
    return -1;
}

配合状态机流程图理解更清晰:

stateDiagram-v2
    [*] --> CheckQuantity
    CheckQuantity --> ValidateExistence: quantity > 0?
    ValidateExistence --> FindDrugByID
    FindDrugByID --> CheckStock: found?
    CheckStock --> DeductStock: stock >= quantity?
    DeductStock --> UpdateStock
    UpdateStock --> [*]: success
    CheckQuantity --> Error: no
    ValidateExistence --> Error: no
    CheckStock --> Error: no
    Error --> [*]

这个状态机告诉我们: 任何高风险操作都应该是“验证先行,执行在后” 。就像飞机起飞前的检查清单,一步都不能少。


查询统计:让数据说话

录入和维护只是基础,真正体现系统价值的是数据分析能力。我们来看看两个高频需求的实现。

多维度查询接口

void search_by_name(const char *keyword) {
    printf("🔍 搜索结果(关键词: %s):\n", keyword);
    for (int i = 0; i < drug_count; ++i) {
        if (strstr(drugs[i].name, keyword)) {
            printf("ID:%d 名称:%s 规格:%s 价格:%.2f 库存:%d\n",
                   drugs[i].id, drugs[i].name, drugs[i].spec,
                   drugs[i].price, drugs[i].stock);
        }
    }
}

void list_low_stock(int threshold) {
    printf("⚠️  低库存预警(≤%d):\n", threshold);
    for (int i = 0; i < drug_count; ++i) {
        if (drugs[i].stock <= threshold) {
            printf("   ▶ %s [ID:%d] 当前库存:%d\n",
                   drugs[i].name, drugs[i].id, drugs[i].stock);
        }
    }
}

虽然只是简单的线性扫描,但在千级数据量下性能完全够用。更重要的是,这些功能直击用户痛点:

  • 医生想找某种抗生素,输入“霉素”就能列出所有相关药品;
  • 药剂科主任每天上班第一件事就是看低库存清单,及时安排采购。

销售统计模块

引入全局变量追踪经营状况:

float total_sales_amount = 0.0f;
int total_sales_count = 0;

void record_sale(int id, int quantity) {
    for (int i = 0; i < drug_count; ++i) {
        if (drugs[i].id == id) {
            total_sales_amount += drugs[i].price * quantity;
            total_sales_count += 1;
            break;
        }
    }
}

void print_sales_report() {
    printf("\n📊 销售统计报告\n");
    printf("────────────────────\n");
    printf("累计交易次数: %d\n", total_sales_count);
    printf("总销售额: ¥%.2f\n", total_sales_amount);
    printf("平均客单价: ¥%.2f\n", total_sales_count ? 
           total_sales_amount / total_sales_count : 0);
}

这些数据不仅能生成日报,还可以导出给财务系统对接。随着业务增长,未来甚至可以加入按月份、品类的多维分析。


工程化升级:从脚本到产品的跨越

写到这里,我们的系统已经具备基本功能。但离真正“可用”还差一口气——那就是 工程化构建

用户输入防护:别让一个回车毁掉一切

你知道最常导致C程序崩溃的原因是什么吗?不是算法错误,而是 输入处理不当

// ❌ 危险!gets() 已被弃用
// gets(buffer);

// ✅ 安全替代方案
char buffer[64];
if (fgets(buffer, sizeof(buffer), stdin)) {
    buffer[strcspn(buffer, "\n")] = 0;  // 安全去换行
}

更进一步,封装一个通用的安全读取函数:

int safe_read_int(const char* prompt, int min, int max) {
    char buf[32];
    int val;
    while (1) {
        printf("%s (%d-%d): ", prompt, min, max);
        if (!fgets(buf, sizeof(buf), stdin)) continue;
        buf[strcspn(buf, "\n")] = 0;

        if (sscanf(buf, "%d", &val) == 1 && val >= min && val <= max)
            return val;

        printf("❌ 请输入 %d 到 %d 之间的整数!\n", min, max);
    }
}

这个函数解决了三大难题:
- 输入超长 → fgets 截断保护;
- 输入字母 → sscanf 返回失败;
- 数值越界 → 显式范围检查;

用户体验瞬间提升好几个档次!

模块拆分与自动化编译

当代码超过500行,单文件开发就变得难以维护。这时候就要祭出 多文件分离大法

graph TD
    A[main.c] --> B[data_manager.h/c]
    A --> C[ui_handler.h/c]
    A --> D[file_io.h/c]

    B --> E[药品增删改查]
    C --> F[菜单显示与输入]
    D --> G[文件读写]

每个模块对外暴露清晰接口,内部实现隐藏。例如 data_manager.h

#ifndef DATA_MANAGER_H
#define DATA_MANAGER_H

#include "medicine.h"

extern Medicine drugs[100];
extern int drug_count;

void init_system(void);
int add_drug(int id, const char* name, const char* spec, 
             float price, int stock);
Medicine* find_by_id(int id);

#endif

然后用 Makefile 自动化编译:

CC = gcc
CFLAGS = -Wall -Wextra -g -std=c99
OBJS = main.o data_manager.o ui_handler.o file_io.o
TARGET = pharma_sys

$(TARGET): $(OBJS)
    $(CC) $(CFLAGS) -o $(TARGET) $(OBJS)

%.o: %.c %.h
    $(CC) $(CFLAGS) -c $< -o $@

clean:
    rm -f $(OBJS) $(TARGET)

.PHONY: clean

敲一句 make ,全自动完成增量编译。改了一个函数?只重新编译对应的目标文件,效率飞起🚀

代码规范与调试支持

最后提两点软实力:

  1. 命名一致性
    ```c
    // 推荐
    int drug_count;
    void display_inventory(void);

// 避免
int dc;
void showInv();
```

  1. 条件式日志输出
    ```c
    #ifdef DEBUG
    #define LOG(fmt, …) printf(“[DEBUG] ” fmt “\n”, ## VA_ARGS )
    #else
    #define LOG(fmt, …)
    #endif

// 使用
LOG(“正在添加药品 ID=%d”, id);
```

发布时加上 -DDEBUG 编译即可开启详细日志,排查问题事半功倍。


这套药品管理系统,从最简单的结构体定义,到复杂的业务风控,再到工程化构建,完整展现了C语言项目的真实开发流程。它不依赖任何第三方库,却具备企业级应用的基本素质:稳定、高效、易维护。

更重要的是,通过这样一个项目,你能深刻体会到 系统思维 的价值——优秀的软件从来不是功能的堆砌,而是对资源、性能、用户体验的综合平衡。

下次当你走进药店,看着收银员熟练地扫码出药,不妨想想:说不定背后跑的就是类似这样的C程序呢 😉

本文还有配套的精品资源,点击获取 menu-r.4af5f7ec.gif

简介:本项目是一个使用C语言开发的药品管理系统,旨在帮助学生巩固C语言基础知识并提升实际编程能力。系统涵盖药品入库、出库、库存查询、销售统计等核心功能,涉及结构体、数组、文件操作、模块化设计和用户交互界面等关键技术。项目采用结构化编程思想,包含主程序、数据定义、文件读写、功能模块和错误处理等多个组件,代码经过测试可运行,适合作为高校C语言课程设计或大作业参考。通过该实践,学生能够深入理解数据结构与程序设计逻辑,提升软件开发的综合能力。


本文还有配套的精品资源,点击获取
menu-r.4af5f7ec.gif

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值