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

简介:”C++自动售货机”是一个典型的控制台应用程序,模拟现实生活中的自动售货机功能,旨在帮助学生通过实际项目掌握C++编程语言,特别是面向对象编程(OOP)的核心概念。项目涵盖变量、控制结构、类与对象、继承与多态、文件操作、异常处理等内容,并引入了简单的用户交互设计与测试方法。通过该项目的实践,学生可以提升在实际场景中运用C++进行模块化开发的能力,并掌握软件工程的基本流程。
C++自动售货机

1. C++自动售货机项目概述与开发准备

在本章中,我们将从项目背景出发,逐步引入自动售货机系统的开发目标与核心功能模块。通过本项目,读者将掌握如何使用C++构建一个结构清晰、模块化良好的控制台应用程序。

1.1 项目背景与开发目标

随着嵌入式系统与自助服务设备的普及,自动售货机作为典型的人机交互设备,具备商品管理、用户交互、支付处理等基本功能。本项目旨在通过C++语言,实现一个模拟自动售货机行为的控制台程序,帮助开发者深入理解面向对象编程、模块化设计以及系统状态管理等关键技术。

1.2 核心业务流程与功能模块

自动售货机系统主要包括以下几个核心模块:

模块名称 功能描述
商品管理 添加、删除、查询商品信息
用户交互 提供菜单界面,处理用户输入
支付处理 计算金额、处理付款与找零
状态管理 维护库存与资金变化
日志与持久化 记录销售信息并保存至文件

通过这些模块的协同工作,系统能够模拟完整的售货流程:用户选择商品 → 投入金额 → 系统判断是否足够 → 完成交易并找零。

1.3 C++在项目中的适用性分析

C++以其高性能、面向对象支持、丰富的标准库和跨平台能力,成为本项目的理想开发语言。相较于其他语言如Python,C++更适合模拟底层逻辑与资源管理,有助于开发者理解内存分配、类结构设计与异常处理机制。

1.4 开发环境搭建

为顺利进行项目开发,需配置如下开发环境:

  • 编译器:GCC / Clang / MSVC
  • IDE:Visual Studio / VS Code / CLion
  • 构建工具:CMake(推荐)
  • 操作系统:Windows / Linux / macOS 均可

以 VS Code + GCC 为例,配置步骤如下:

# 安装 GCC 编译器(Ubuntu)
sudo apt update
sudo apt install build-essential

# 安装 VS Code 插件
code --install-extension ms-vscode.cpptools

配置完成后,即可创建第一个 C++ 源文件 main.cpp 并运行测试:

#include <iostream>

int main() {
    std::cout << "欢迎使用自动售货机系统!" << std::endl;
    return 0;
}

执行命令:

g++ main.cpp -o vending_machine
./vending_machine

输出结果:

欢迎使用自动售货机系统!

至此,项目开发环境已准备就绪,后续章节将围绕核心功能模块逐步展开实现。

2. C++基础语法与控制结构的应用

在开发自动售货机系统的过程中,C++的基础语法与控制结构是构建程序逻辑的基石。本章将深入探讨C++中的基本数据类型、变量声明、运算符使用、控制结构设计以及函数的定义与调用。通过这些基础知识,我们可以构建出结构清晰、逻辑严谨的程序模块,为后续面向对象编程与系统功能实现打下坚实基础。

2.1 基本数据类型与变量声明

在C++中,数据类型的正确选择与变量的合理声明是程序设计的第一步。自动售货机系统中涉及的商品价格、库存数量、用户输入等都需要使用合适的数据类型来表示。

2.1.1 整型、浮点型与字符型的使用

C++中常用的整型包括 int short long ,浮点型有 float double ,字符型为 char 。在自动售货机系统中,商品编号通常使用 int 类型,价格使用 double ,而用户输入的选择项则可以使用 char int

#include <iostream>
using namespace std;

int main() {
    int productID = 1001;         // 商品编号
    double price = 3.50;          // 商品价格
    char choice = 'A';            // 用户选项

    cout << "商品编号:" << productID << endl;
    cout << "价格:" << price << endl;
    cout << "用户选择:" << choice << endl;

    return 0;
}

代码分析:

  • int productID = 1001; :定义整型变量表示商品编号。
  • double price = 3.50; :使用双精度浮点型表示价格,避免浮点误差。
  • char choice = 'A'; :字符型用于表示用户菜单选择。
  • cout 输出变量内容,验证数据类型的使用。

表格:C++常见数据类型大小(32位平台)

数据类型 大小(字节) 范围示例
char 1 -128 ~ 127
int 4 -2,147,483,648 ~ 2,147,483,647
float 4 ±3.4e-38 ~ ±3.4e38
double 8 ±1.7e-308 ~ ±1.7e308

2.1.2 变量的作用域与生命周期

变量的作用域决定了其在程序中可见的范围,生命周期则决定了变量在内存中存在的时间。

在自动售货机系统中,局部变量用于函数内部临时计算,全局变量可用于记录库存数量或系统状态。

#include <iostream>
using namespace std;

int totalSales = 0;  // 全局变量,记录总销售金额

void sellProduct(double price) {
    int quantity = 2;           // 局部变量,仅在函数内部可见
    double total = price * quantity;
    totalSales += total;        // 修改全局变量
    cout << "本次销售金额:" << total << endl;
}

int main() {
    sellProduct(3.50);
    cout << "累计销售总额:" << totalSales << endl;
    return 0;
}

代码分析:

  • totalSales 是全局变量,生命周期贯穿整个程序运行。
  • quantity 是局部变量,仅在 sellProduct 函数内部有效。
  • total 为函数内部计算变量,函数执行结束后销毁。

mermaid 流程图:变量作用域与生命周期流程

graph TD
    A[程序开始] --> B[定义全局变量 totalSales]
    B --> C[进入 main 函数]
    C --> D[调用 sellProduct]
    D --> E[定义局部变量 quantity]
    D --> F[计算 total 并修改 totalSales]
    D --> G[函数返回,释放局部变量]
    C --> H[输出 totalSales]
    H --> I[程序结束,释放全局变量]

2.2 运算符与表达式

运算符是C++中实现数据操作的重要工具,表达式则是程序逻辑的基本单元。在自动售货机系统中,我们需要频繁使用算术、比较和逻辑运算来处理用户输入、库存判断、金额计算等操作。

2.2.1 算术运算与比较运算

自动售货机中常见的操作包括:计算总价、判断库存是否充足、检查用户余额是否足够等。

#include <iostream>
using namespace std;

int main() {
    int stock = 5;
    double balance = 10.0;
    double price = 3.5;

    double totalPrice = price * 2;  // 算术运算
    bool hasEnoughStock = (stock >= 2);  // 比较运算
    bool canAfford = (balance >= totalPrice);

    if (hasEnoughStock && canAfford) {
        cout << "购买成功,总价:" << totalPrice << endl;
    } else {
        cout << "购买失败,请检查库存或余额" << endl;
    }

    return 0;
}

代码分析:

  • price * 2 :使用乘法运算符计算总价。
  • (stock >= 2) :比较运算判断库存是否足够。
  • balance >= totalPrice :比较用户余额是否足够。
  • && :逻辑与,用于组合多个条件。

表格:常用运算符及其功能

运算符 类型 示例 说明
+ 算术 a + b 加法
> 比较 a > b 大于
&& 逻辑 a && b
== 比较 a == b 等于
++ 自增 i++ 自增1

2.2.2 逻辑运算与条件判断

逻辑运算符在自动售货机的判断逻辑中尤为重要,例如判断用户是否满足购买条件。

#include <iostream>
using namespace std;

int main() {
    bool isVIP = true;
    double balance = 2.0;
    double price = 3.0;

    if ((isVIP || balance >= price) && balance >= price * 0.8) {
        cout << "允许购买(VIP或有足够余额)" << endl;
    } else {
        cout << "余额不足,无法购买" << endl;
    }

    return 0;
}

代码分析:

  • isVIP || balance >= price :判断是否为VIP或余额足够。
  • balance >= price * 0.8 :进一步判断是否有8折优惠资格。
  • && :两个条件必须同时满足。

2.3 控制结构的实现

控制结构决定了程序的执行路径,是自动售货机系统中实现菜单选择、用户交互、操作循环等逻辑的核心。

2.3.1 条件语句(if-else、switch)在菜单选择中的应用

自动售货机通常提供多个商品选择,使用 if-else switch 实现选择逻辑。

#include <iostream>
using namespace std;

int main() {
    int choice;
    cout << "请选择商品:1.矿泉水 2.可乐 3.果汁" << endl;
    cin >> choice;

    switch (choice) {
        case 1:
            cout << "您选择了矿泉水,价格2元" << endl;
            break;
        case 2:
            cout << "您选择了可乐,价格3元" << endl;
            break;
        case 3:
            cout << "您选择了果汁,价格4元" << endl;
            break;
        default:
            cout << "无效选择" << endl;
    }

    return 0;
}

代码分析:

  • switch 根据用户输入选择商品。
  • case 分支处理不同选项。
  • default 处理无效输入。

2.3.2 循环结构(for、while、do-while)处理用户操作

用户可能需要多次操作,使用循环结构可以持续接收输入。

#include <iostream>
using namespace std;

int main() {
    int choice;
    do {
        cout << "1.购买商品 2.查看余额 3.退出" << endl;
        cin >> choice;

        switch (choice) {
            case 1:
                cout << "开始购买流程..." << endl;
                break;
            case 2:
                cout << "当前余额:5.0元" << endl;
                break;
            case 3:
                cout << "退出系统" << endl;
                break;
            default:
                cout << "无效选项" << endl;
        }
    } while (choice != 3);

    return 0;
}

代码分析:

  • do-while 确保至少执行一次菜单显示。
  • 用户输入 3 时退出循环。

mermaid 流程图:循环结构执行流程

graph TD
    A[开始] --> B[显示菜单]
    B --> C[获取用户输入]
    C --> D{选择是否为3?}
    D -->|是| E[退出循环]
    D -->|否| F[执行对应操作]
    F --> G[继续循环]
    G --> B

2.3.3 break、continue与goto在流程控制中的使用技巧

在复杂控制结构中, break continue 用于流程控制, goto 虽不推荐但也有特定用途。

#include <iostream>
using namespace std;

int main() {
    for (int i = 1; i <= 5; i++) {
        if (i == 3) {
            continue;  // 跳过3
        }
        if (i == 5) {
            break;     // 终止循环
        }
        cout << i << " ";
    }

    // 使用 goto 模拟错误处理跳转
    int errorCode = 1;
    if (errorCode != 0) {
        goto error;
    }
    cout << "正常结束" << endl;
    return 0;

error:
    cout << "发生错误,跳转处理" << endl;
    return -1;
}

代码分析:

  • continue :跳过数字3。
  • break :提前终止循环。
  • goto :跳转到错误处理块,适用于异常流程处理(不推荐频繁使用)。

2.4 函数基础与参数传递

函数是程序模块化的基本单位。自动售货机系统中大量的功能都可以封装为函数,提高代码复用性和可维护性。

2.4.1 函数定义与调用

我们以一个简单的购买函数为例,展示函数的定义与调用。

#include <iostream>
using namespace std;

// 函数声明
void purchase(int productID, double price, int quantity);

int main() {
    purchase(101, 3.5, 2);  // 函数调用
    return 0;
}

// 函数定义
void purchase(int productID, double price, int quantity) {
    double total = price * quantity;
    cout << "商品编号:" << productID << ",总价:" << total << endl;
}

代码分析:

  • purchase 函数封装购买逻辑。
  • 参数包括商品编号、价格和数量。
  • 函数内部计算总价并输出。

2.4.2 参数传递方式与返回值设计

C++支持值传递、引用传递和指针传递。自动售货机中库存修改需要引用或指针传递。

#include <iostream>
using namespace std;

void updateStock(int& stock, int sold) {
    stock -= sold;
}

int main() {
    int stock = 10;
    updateStock(stock, 2);
    cout << "剩余库存:" << stock << endl;
    return 0;
}

代码分析:

  • int& stock :使用引用传递,函数可修改原始变量。
  • stock -= sold :更新库存值。

表格:参数传递方式对比

传递方式 是否修改原始值 适用场景
值传递 只读数据
引用传递 需要修改原始数据
指针传递 需要动态内存或数组操作

本章从C++的基本语法入手,逐步深入讲解了数据类型、变量作用域、运算符使用、控制结构与函数设计,结合自动售货机系统的实际应用场景,展示了如何通过基础语法构建程序逻辑。这些内容为后续面向对象编程和系统模块化设计打下了坚实基础。

3. 面向对象编程(OOP)与类的设计实践

在C++项目开发中,面向对象编程(Object-Oriented Programming,简称OOP)是一种核心设计思想,它通过将数据与操作封装为对象,提高了代码的可维护性、可扩展性和复用性。自动售货机项目作为一个具备多个功能模块的系统,非常适合采用OOP进行模块化设计。本章将从类与对象的基本概念出发,逐步深入到封装、继承、多态等面向对象编程的核心机制,并结合自动售货机的业务需求,展示如何在实际项目中设计类结构,提升代码组织能力与系统扩展性。

3.1 类与对象的基本概念

类(Class)是面向对象编程的基本构建块,它定义了对象的属性和行为。对象(Object)则是类的实例,是程序运行时的数据实体。在自动售货机系统中,商品、用户、库存、支付等都可以被抽象为类。

3.1.1 类的定义与成员变量

类的定义包括类名、成员变量和成员函数。成员变量用于描述类的属性,例如商品类中的名称、价格、库存数量等。

class Product {
private:
    std::string name;     // 商品名称
    double price;         // 商品价格
    int stock;            // 库存数量

public:
    // 构造函数、成员函数等
};

上述代码定义了一个 Product 类,包含三个私有成员变量。使用 private 关键字可以限制外部直接访问这些变量,从而实现封装。

3.1.2 成员函数与构造函数设计

成员函数是类的行为,用于操作类的数据。构造函数用于初始化对象。构造函数的名称与类名相同,没有返回类型。

class Product {
private:
    std::string name;
    double price;
    int stock;

public:
    // 构造函数
    Product(const std::string& n, double p, int s)
        : name(n), price(p), stock(s) {}

    // 获取商品名称
    std::string getName() const {
        return name;
    }

    // 获取商品价格
    double getPrice() const {
        return price;
    }

    // 获取库存数量
    int getStock() const {
        return stock;
    }

    // 减少库存
    void reduceStock() {
        if (stock > 0) {
            stock--;
        }
    }
};

代码分析:

  • Product(const std::string& n, double p, int s) 是构造函数,使用初始化列表对成员变量进行初始化。
  • getName() getPrice() getStock() 是 Getter 方法,用于获取私有成员变量的值。
  • reduceStock() 方法用于在用户购买商品时减少库存,同时加入了库存检查逻辑。
表格:构造函数与普通函数对比
特性 构造函数 普通函数
函数名 与类名相同 可自定义
返回类型
是否自动调用
用途 初始化对象 执行操作
可重载

3.2 封装机制与访问控制

封装(Encapsulation)是面向对象编程的三大特性之一,通过将数据设为私有,并提供公开的接口来访问和修改数据,可以有效保护数据安全并提高模块化程度。

3.2.1 public、private与protected访问权限

在C++中,类成员的访问权限有三种:

  • public :公共访问权限,类内外均可访问。
  • private :私有访问权限,仅类内部可访问。
  • protected :受保护访问权限,类内部和派生类可访问。
class Product {
private:
    std::string name;
    double price;
    int stock;

protected:
    std::string category; // 分类信息,仅派生类可用

public:
    Product(const std::string& n, double p, int s)
        : name(n), price(p), stock(s) {}

    std::string getName() const { return name; }
    double getPrice() const { return price; }
    int getStock() const { return stock; }

    void setPrice(double newPrice) {
        if (newPrice > 0) {
            price = newPrice;
        }
    }
};

代码分析:

  • name price stock 设置为 private ,防止外部直接修改。
  • setPrice() 是 Setter 方法,允许外部在合法范围内修改价格。
  • category 被设为 protected ,可供继承类访问。

3.2.2 Getter与Setter方法的设计规范

Getter 和 Setter 方法用于访问和修改私有成员变量。它们应遵循以下规范:

  • 命名规范 :通常为 getXXX() setXXX()
  • 参数检查 :Setter 方法应进行参数合法性校验。
  • 常量函数 :Getter 方法应声明为 const ,以确保不修改对象状态。
// Getter 方法示例
double getPrice() const {
    return price;
}

// Setter 方法示例
void setPrice(double newPrice) {
    if (newPrice > 0) {
        price = newPrice;
    }
}
流程图:封装与访问控制流程
graph TD
    A[外部请求访问私有数据] --> B{是否有Getter方法?}
    B -->|是| C[调用Getter返回值]
    B -->|否| D[访问被拒绝]
    E[外部请求修改私有数据] --> F{是否有Setter方法?}
    F -->|是| G[调用Setter并校验参数]
    F -->|否| H[修改被拒绝]

3.3 类的继承与多态实现

继承(Inheritance)和多态(Polymorphism)是面向对象编程的另外两个核心特性。继承用于构建类之间的层次结构,而多态则允许子类重写父类的方法,实现不同的行为。

3.3.1 继承关系设计(如商品类与饮料类)

自动售货机中的商品可分为多个类别,如饮料、零食、日用品等。我们可以设计一个基类 Product ,然后通过继承创建具体的子类,如 Beverage

class Beverage : public Product {
private:
    int volume; // 容量(毫升)

public:
    Beverage(const std::string& name, double price, int stock, int vol)
        : Product(name, price, stock), volume(vol) {}

    int getVolume() const {
        return volume;
    }
};

代码分析:

  • Beverage 继承自 Product ,使用 public 继承方式。
  • 构造函数通过初始化列表调用父类构造函数。
  • 新增了 volume 成员变量用于描述饮料容量。
表格:继承类型与访问权限
继承方式 父类成员访问权限 子类访问权限
public public public
public protected protected
public private 不可访问
protected public protected
protected protected protected
private public private

3.3.2 虚函数与运行时多态在菜单处理中的应用

多态允许子类重写父类的方法,从而在运行时根据对象的实际类型决定调用哪个方法。这在菜单系统中非常有用,比如不同商品类型在展示时可以有不同的格式。

class Product {
public:
    virtual void display() const {
        std::cout << "商品信息:" << std::endl;
    }
};

class Beverage : public Product {
public:
    void display() const override {
        std::cout << "饮料 - 容量: " << volume << "ml" << std::endl;
    }
};

class Snack : public Product {
public:
    void display() const override {
        std::cout << "零食 - 重量: " << weight << "g" << std::endl;
    }
};

代码分析:

  • virtual void display() 是虚函数,表示该方法可以在子类中被重写。
  • override 关键字用于明确表示该方法是对父类方法的重写。
  • 使用基类指针或引用调用 display() 时,会根据实际对象类型调用相应实现。
流程图:多态调用流程
graph TD
    A[定义基类指针指向子类对象] --> B{调用虚函数display()}
    B --> C[运行时查找虚函数表]
    C --> D[调用实际对象的实现方法]

3.4 静态成员与常量成员

静态成员和常量成员用于处理与对象实例无关或不可变的数据。它们在某些系统状态管理、常量配置等场景中非常有用。

3.4.1 静态变量与函数在状态管理中的使用

静态成员属于类本身,而不是类的实例。它们常用于存储所有对象共享的数据,例如自动售货机的总收入。

class VendingMachine {
private:
    static double totalRevenue; // 静态变量,记录总收入

public:
    static double getTotalRevenue() {
        return totalRevenue;
    }

    void sellProduct(double price) {
        totalRevenue += price;
    }
};

// 必须在类外初始化静态变量
double VendingMachine::totalRevenue = 0.0;

代码分析:

  • static double totalRevenue 是一个静态变量,属于类,而不是某个具体对象。
  • getTotalRevenue() 是静态成员函数,用于访问静态变量。
  • 静态函数只能访问静态成员变量。
表格:静态成员与普通成员对比
特性 静态成员 普通成员
所属对象 类本身 类的实例
初始化方式 必须在类外初始化 构造函数中初始化
访问方式 类名::成员名 对象.成员名 / 指针->成员名
可访问成员 只能访问静态成员 可访问所有成员

3.4.2 const成员函数与不可变对象的设计

const 成员函数用于确保不修改对象的状态,适用于只读操作,如获取对象属性。

class Product {
private:
    std::string name;
    double price;

public:
    std::string getName() const {
        return name;
    }

    double getPrice() const {
        return price;
    }
};

代码分析:

  • getName() getPrice() 被声明为 const 函数,表示它们不会修改对象状态。
  • 如果尝试在 const 函数中修改成员变量,编译器将报错。
流程图:const成员函数调用流程
graph TD
    A[定义const对象] --> B{调用const成员函数}
    B --> C[允许访问只读方法]
    D[调用非const成员函数] --> E[编译错误]

4. 系统功能模块化与高级特性应用

现代软件开发中,模块化设计和高级语言特性的合理使用对于提升代码质量、可维护性及扩展性至关重要。在本章中,我们将深入探讨如何在自动售货机项目中应用单例模式进行全局状态管理、如何使用文件读写实现数据持久化、如何构建异常处理机制以提升用户输入的健壮性,并优化控制台用户界面交互体验。通过这些高级特性的应用,我们可以使系统结构更加清晰,功能更加完善。

4.1 单例模式与全局状态管理

单例模式(Singleton Pattern)是一种创建型设计模式,确保一个类只有一个实例,并提供一个全局访问点。在自动售货机项目中,库存管理、资金统计等功能通常需要在整个系统中保持唯一状态,因此非常适合使用单例模式进行封装。

4.1.1 单例模式的实现原理与应用场景

单例模式的核心思想是私有化构造函数与拷贝构造函数,防止外部创建多个实例。通过静态方法提供唯一实例的访问接口。

示例代码:

class VendingMachine {
private:
    static VendingMachine* instance;
    int balance;  // 当前余额
    std::map<std::string, int> inventory;  // 商品库存

    VendingMachine() : balance(0) {}

public:
    // 禁止拷贝
    VendingMachine(const VendingMachine&) = delete;
    VendingMachine& operator=(const VendingMachine&) = delete;

    static VendingMachine* getInstance() {
        if (instance == nullptr) {
            instance = new VendingMachine();
        }
        return instance;
    }

    void addFunds(int amount) {
        balance += amount;
    }

    int getBalance() const {
        return balance;
    }

    void addInventory(const std::string& item, int count) {
        inventory[item] += count;
    }

    void displayInventory() const {
        for (const auto& pair : inventory) {
            std::cout << pair.first << ": " << pair.second << " units" << std::endl;
        }
    }
};

// 初始化静态成员变量
VendingMachine* VendingMachine::instance = nullptr;

逐行解读分析:

  • 第1行:定义 VendingMachine 类,用于管理自动售货机的全局状态。
  • 第2~4行:声明静态实例指针、余额和库存字典。
  • 第5~6行:私有构造函数和初始化列表,防止外部实例化。
  • 第8~9行:删除拷贝构造函数和赋值操作符,避免对象复制。
  • 第11~16行: getInstance() 方法用于获取单例实例,首次调用时创建。
  • 第18~29行:提供资金管理与库存管理接口。
  • 第32行:初始化静态变量 instance nullptr

应用场景:

  • 库存管理 :确保商品库存在整个系统中只有一个全局状态。
  • 资金管理 :跟踪用户投入金额与找零状态。
  • 日志记录 :确保日志系统在整个项目中只有一个实例。

mermaid流程图:

graph TD
    A[用户操作] --> B[调用VendingMachine::getInstance()]
    B --> C{实例是否存在?}
    C -->|是| D[返回已有实例]
    C -->|否| E[创建新实例]
    E --> F[初始化库存与余额]
    D --> G[调用 addFunds 或 addInventory]
    G --> H[更新全局状态]

4.1.2 使用单例管理自动售货机库存与资金

在实际项目中,我们可以通过单例实例来统一管理库存和资金。例如,当用户投币时,调用 addFunds() 方法更新余额;当售出商品时,调用 addInventory() 减少库存。

int main() {
    VendingMachine* machine = VendingMachine::getInstance();
    machine->addInventory("Coke", 5);
    machine->addInventory("Water", 10);
    machine->displayInventory();

    machine->addFunds(50);
    std::cout << "Current balance: $" << machine->getBalance() << std::endl;

    return 0;
}

输出结果:

Coke: 5 units
Water: 10 units
Current balance: $50

逻辑分析:

  • 第2行:获取单例实例。
  • 第4~5行:添加商品库存。
  • 第6行:展示当前库存。
  • 第8~9行:模拟用户投币并显示余额。

通过这种方式,我们可以确保整个项目中只有一个库存和资金的全局状态,便于维护与调试。

4.2 文件读写与状态持久化

为了防止程序重启后丢失库存与销售记录,我们需要将关键数据持久化到文件中。C++中提供了 ifstream ofstream 类来实现文件读写功能。

4.2.1 商品信息与销售记录的文件存储

我们可以将商品库存信息保存到 inventory.txt 中,销售记录保存到 sales.log 中。

示例文件格式:

inventory.txt

Coke 5
Water 10
Chips 3

sales.log

2025-04-05 14:30: Sold 1 x Coke, Balance: $50

4.2.2 ifstream与ofstream的使用方法

写入库存信息示例:

void saveInventory(const std::map<std::string, int>& inventory, const std::string& filename) {
    std::ofstream outFile(filename);
    if (!outFile.is_open()) {
        std::cerr << "Failed to open file: " << filename << std::endl;
        return;
    }

    for (const auto& pair : inventory) {
        outFile << pair.first << " " << pair.second << std::endl;
    }

    outFile.close();
}

逐行解读:

  • 第1行:函数接收库存和文件名。
  • 第2行:打开文件进行写入。
  • 第3~5行:错误处理,判断是否成功打开。
  • 第7~9行:将库存信息写入文件。
  • 第11行:关闭文件。

读取库存信息示例:

std::map<std::string, int> loadInventory(const std::string& filename) {
    std::map<std::string, int> inventory;
    std::ifstream inFile(filename);
    if (!inFile.is_open()) {
        std::cerr << "Failed to open file: " << filename << std::endl;
        return inventory;
    }

    std::string item;
    int count;
    while (inFile >> item >> count) {
        inventory[item] = count;
    }

    inFile.close();
    return inventory;
}

参数说明:

  • filename :要读取的文件路径。
  • 返回值:包含商品与库存数量的 map 容器。

表格:文件操作类功能对比

类名 功能描述 常用方法
ifstream 用于读取文件 open() , is_open() , >>
ofstream 用于写入文件 open() , is_open() , <<
fstream 同时支持读写操作 open() , is_open()

通过上述方法,我们可以实现商品信息的持久化管理,即使程序重启,也能恢复之前的库存状态。

4.3 异常处理机制设计

在用户输入或文件读写过程中,可能会遇到各种异常情况,例如无效输入、文件不存在等。为了增强程序的健壮性,我们需要引入异常处理机制。

4.3.1 try-catch块在用户输入错误处理中的应用

#include <stdexcept>

void processUserInput() {
    try {
        std::cout << "Enter amount to add: ";
        int amount;
        std::cin >> amount;

        if (std::cin.fail()) {
            throw std::invalid_argument("Invalid input. Please enter a number.");
        }

        VendingMachine::getInstance()->addFunds(amount);
    } catch (const std::exception& e) {
        std::cerr << "Error: " << e.what() << std::endl;
        std::cin.clear();  // 清除错误标志
        std::cin.ignore(std::numeric_limits<std::streamsize>::max(), '\n');  // 忽略错误输入
    }
}

逻辑分析:

  • 第5~8行:尝试读取用户输入。
  • 第9~11行:如果输入失败,抛出异常。
  • 第13~17行:捕获异常并提示用户,同时清除输入流错误。

4.3.2 自定义异常类与错误码机制

为了更细粒度地处理错误,我们可以自定义异常类:

class VendingException : public std::exception {
private:
    int errorCode;
    std::string message;

public:
    VendingException(int code, const std::string& msg) : errorCode(code), message(msg) {}

    const char* what() const noexcept override {
        return message.c_str();
    }

    int getCode() const {
        return errorCode;
    }
};

使用示例:

if (amount <= 0) {
    throw VendingException(1001, "Amount must be positive.");
}

参数说明:

  • errorCode :表示错误类型,便于程序处理。
  • message :描述错误信息,便于用户理解。

表格:标准异常类与自定义异常类对比

异常类 来源 适用场景
std::exception 标准库 通用异常基类
std::invalid_argument 标准库 输入参数无效
VendingException 自定义 自动售货机专用异常

通过异常处理机制,我们可以有效捕获并处理运行时错误,提高系统的稳定性和用户体验。

4.4 控制台用户界面交互设计

控制台界面虽然简单,但良好的交互设计可以显著提升用户体验。本节将介绍清屏与菜单刷新技巧、用户输入处理与状态反馈机制。

4.4.1 清屏与菜单刷新技巧

在 Windows 系统中,可以使用 system("cls") 实现清屏;在 Linux 系统中使用 system("clear")

void clearScreen() {
    #ifdef _WIN32
        system("cls");
    #else
        system("clear");
    #endif
}

void displayMenu() {
    clearScreen();
    std::cout << "=== Vending Machine Menu ===\n"
              << "1. Add Funds\n"
              << "2. Select Item\n"
              << "3. View Inventory\n"
              << "4. Exit\n"
              << "============================\n"
              << "Enter your choice: ";
}

逻辑分析:

  • clearScreen() 函数根据操作系统选择清屏命令。
  • displayMenu() 函数用于刷新菜单界面,提升用户交互流畅度。

4.4.2 用户输入处理与状态反馈机制

用户操作后应立即反馈状态信息,例如余额更新、库存变化等。

void handleUserChoice(int choice) {
    switch (choice) {
        case 1: {
            int amount;
            std::cout << "Enter amount to add: ";
            std::cin >> amount;
            if (std::cin.fail() || amount <= 0) {
                std::cerr << "Invalid amount.\n";
                break;
            }
            VendingMachine::getInstance()->addFunds(amount);
            std::cout << "Added $" << amount << ". Current balance: $"
                      << VendingMachine::getInstance()->getBalance() << std::endl;
            break;
        }
        case 2:
            // 商品选择逻辑
            break;
        case 3:
            VendingMachine::getInstance()->displayInventory();
            break;
        case 4:
            std::cout << "Thank you for using the vending machine!\n";
            exit(0);
        default:
            std::cerr << "Invalid choice. Please try again.\n";
    }
}

逻辑分析:

  • 每个菜单选项对应不同的处理逻辑。
  • 操作后立即反馈余额或库存变化,增强用户感知。

mermaid流程图:

graph TD
    A[显示菜单] --> B{用户选择}
    B -->|添加资金| C[输入金额]
    C --> D{金额是否合法?}
    D -->|是| E[更新余额并显示]
    D -->|否| F[提示错误]
    B -->|查看库存| G[调用 displayInventory()]
    B -->|退出| H[退出程序]

通过良好的界面设计与交互反馈机制,我们可以提升自动售货机系统的可用性与用户体验。

本章通过单例模式实现全局状态管理、文件读写实现数据持久化、异常处理机制增强系统健壮性,并优化控制台交互界面,为自动售货机项目提供了完整的高级功能支持。下一章将继续整合各模块并进行系统测试。

5. 项目整合与系统测试

5.1 自动售货机核心模块整合

在本项目中,我们已经分别完成了商品管理、支付处理、用户交互等模块的开发。现在,我们需要将这些模块整合成一个完整的自动售货机系统。

5.1.1 商品管理模块与库存更新

商品管理模块主要由 Product 类和 VendingMachine 类组成。 Product 类封装了商品的基本信息,包括名称、价格、库存数量等; VendingMachine 类则负责管理商品的库存,并提供购买接口。

// Product.h
#pragma once
#include <string>

class Product {
public:
    Product(const std::string& name, double price, int stock);
    std::string getName() const;
    double getPrice() const;
    int getStock() const;
    void reduceStock();  // 减少库存
    void addStock(int quantity); // 增加库存
private:
    std::string name;
    double price;
    int stock;
};
// Product.cpp
#include "Product.h"
#include <iostream>

Product::Product(const std::string& name, double price, int stock)
    : name(name), price(price), stock(stock) {}

std::string Product::getName() const { return name; }
double Product::getPrice() const { return price; }
int Product::getStock() const { return stock; }

void Product::reduceStock() {
    if (stock > 0) {
        stock--;
    } else {
        std::cout << "库存不足!" << std::endl;
    }
}

void Product::addStock(int quantity) {
    stock += quantity;
}

VendingMachine 类维护商品列表,并实现购买、显示商品等操作。

// VendingMachine.h
#pragma once
#include <vector>
#include "Product.h"

class VendingMachine {
public:
    void addProduct(const Product& product);
    void displayProducts() const;
    bool purchaseProduct(int index, double money);
private:
    std::vector<Product> products;
};
// VendingMachine.cpp
#include "VendingMachine.h"
#include <iostream>

void VendingMachine::addProduct(const Product& product) {
    products.push_back(product);
}

void VendingMachine::displayProducts() const {
    for (size_t i = 0; i < products.size(); ++i) {
        std::cout << i + 1 << ". " << products[i].getName() 
                  << " 价格:" << products[i].getPrice() 
                  << " 库存:" << products[i].getStock() << std::endl;
    }
}

bool VendingMachine::purchaseProduct(int index, double money) {
    if (index < 1 || index > static_cast<int>(products.size())) {
        std::cout << "无效的选择!" << std::endl;
        return false;
    }

    Product& product = products[index - 1];
    if (money < product.getPrice()) {
        std::cout << "金额不足!" << std::endl;
        return false;
    }

    if (product.getStock() <= 0) {
        std::cout << "该商品已售罄!" << std::endl;
        return false;
    }

    product.reduceStock();
    double change = money - product.getPrice();
    std::cout << "购买成功!找零:" << change << " 元。" << std::endl;
    return true;
}

5.1.2 支付处理模块与找零计算

支付处理模块不仅负责判断用户是否支付足够的金额,还需计算找零。我们将其逻辑集成在 purchaseProduct 方法中,如上例所示。

此外,我们还可以扩展该模块以支持多种支付方式(如投币、扫码支付),这里我们仅实现基础的现金支付。

5.2 单元测试与系统调试

为了确保系统稳定运行,我们需要对核心逻辑进行单元测试,并使用调试工具排查潜在问题。

5.2.1 使用断言验证核心逻辑

我们可以使用 C++ 标准库中的 <cassert> 来进行断言测试,验证商品购买、库存更新等功能是否正常。

// test_main.cpp
#include <cassert>
#include "Product.h"
#include "VendingMachine.h"

void runTests() {
    Product p1("矿泉水", 2.0, 5);
    assert(p1.getName() == "矿泉水");
    assert(p1.getPrice() == 2.0);
    assert(p1.getStock() == 5);

    p1.reduceStock();
    assert(p1.getStock() == 4);

    VendingMachine vm;
    vm.addProduct(p1);
    vm.displayProducts();

    assert(vm.purchaseProduct(1, 3.0));  // 正常购买
    assert(vm.purchaseProduct(1, 1.0) == false); // 金额不足
    assert(vm.purchaseProduct(1, 2.0));  // 成功购买
}

int main() {
    runTests();
    return 0;
}

通过断言测试,我们可以在开发阶段及时发现逻辑错误。

5.2.2 使用调试工具定位运行时错误

在 Visual Studio 或 GDB 中运行调试模式,可以设置断点、查看变量状态、单步执行程序,帮助我们快速定位运行时错误。

例如,如果用户输入了非法的商品编号,程序应该能正确捕获并提示错误。我们可以在 purchaseProduct 中设置断点,观察 index 是否越界。

5.3 完整项目流程演示

5.3.1 用户操作流程模拟与测试

我们将所有模块整合到主程序中,模拟完整的用户交互流程:

// main.cpp
#include <iostream>
#include "VendingMachine.h"
#include "Product.h"

int main() {
    VendingMachine machine;
    machine.addProduct(Product("矿泉水", 2.0, 5));
    machine.addProduct(Product("可乐", 3.0, 3));
    machine.addProduct(Product("咖啡", 4.5, 2));

    while (true) {
        std::cout << "\n=== 自动售货机 ===\n";
        machine.displayProducts();
        std::cout << "请输入商品编号(输入0退出):";
        int choice;
        std::cin >> choice;

        if (choice == 0) break;

        std::cout << "请输入支付金额:";
        double money;
        std::cin >> money;

        machine.purchaseProduct(choice, money);
    }

    return 0;
}

5.3.2 多场景测试与边界条件处理

我们需要测试以下几种典型场景:

场景编号 操作描述 预期结果
1 正常购买 成功购买,库存减少,找零正确
2 金额不足 提示“金额不足”
3 商品已售罄 提示“该商品已售罄”
4 输入非法编号 提示“无效的选择”
5 多次连续购买同一商品 库存逐步减少,最终售罄

通过上述测试,我们可以确保系统在各种边界条件下都能正常运行。

5.4 项目优化建议与扩展方向

5.4.1 性能优化与内存管理

当前系统使用 std::vector 来管理商品,内存管理较为高效。未来可以考虑以下优化:

  • 使用 reserve() 预分配空间,减少动态扩容开销。
  • 对商品类进行 const 成员函数设计,提高数据访问安全性。
  • 使用智能指针(如 std::unique_ptr )来管理动态分配的对象,防止内存泄漏。

5.4.2 未来可扩展的功能模块设想(如网络通信、数据库支持)

为了提升系统的实用性和可扩展性,可以考虑以下功能扩展:

  • 网络通信 :支持远程管理库存、商品信息同步。
  • 数据库支持 :使用 SQLite 或 MySQL 存储商品信息、销售记录,实现数据持久化。
  • GUI 界面 :使用 Qt 或 SDL 构建图形界面,提升用户体验。
  • 多线程支持 :实现并发操作,如同时支持多个用户购买、后台库存更新等。

这些扩展将使自动售货机系统更加贴近实际应用场景,具备更强的实用价值。

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

简介:”C++自动售货机”是一个典型的控制台应用程序,模拟现实生活中的自动售货机功能,旨在帮助学生通过实际项目掌握C++编程语言,特别是面向对象编程(OOP)的核心概念。项目涵盖变量、控制结构、类与对象、继承与多态、文件操作、异常处理等内容,并引入了简单的用户交互设计与测试方法。通过该项目的实践,学生可以提升在实际场景中运用C++进行模块化开发的能力,并掌握软件工程的基本流程。


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

更多推荐