04 图形计算器

04 图形计算器


作者:abnb250/山中鹤鸣
博客:https://www.cnblogs.com/abnb250
交流群:1062464078
点击返回目录



前情提要

这一节用抽象基类 Shape 加 Circle/Rectangle/Square/Triangle 四个派生类,
配合 std::vector<std::unique_ptr<Shape>> 演示多态、RAII 与菜单式交互,另有面积/周长累加、按索引删除等功能。
注释版盯住四处:
Shape.h 的 include guard 宏名与 #ifndef 不匹配,会重复定义;
readdouble 里变量必须声明为 double 而不是 int,否则小数会被截断或拒绝;
showmain 的 cin >> choice 会留下换行,create 里的 getline 会读到空行直接返回;
Triangle 构造里的排序是"疑似反直觉",需确认是否有调用方依赖 a<=b<=c。



代码(加注释版)

注释解释了两种场景:

  1. 代码有反直觉痕迹,自然写法就在旁边。
  2. 代码看起来自然,但背后有隐形决策。

Rectangle.h

#ifndef RECTANGLE_H
#define RECTANGLE_H

#include "Shape.h"

class Rectangle : public Shape{
private:
    double l;
    double s;
public:
    Rectangle(): l(2.0), s(1.0) {}
    Rectangle(double l_, double s_) : l(l_), s(s_) {}
    ~Rectangle() {}
    double area() const override;
    double perimeter() const override;
    string name() const override;
    double get_l() const ;
    double get_s() const;
};

#endif

Rectangle.cpp

#include "Rectangle.h"

double Rectangle::area() const {
    return l*s;
}

double Rectangle::perimeter() const {
    return 2*(l + s);
}

string Rectangle::name() const {
    return "Rectangle";
}

double Rectangle::get_l() const{
    return l;
}

double Rectangle::get_s() const{
    return s;
}

Shape.h

#ifndef SHAPE_H
// 不能把宏名写成 SHAPEH:与 #ifndef SHAPE_H 不匹配,重复包含时 Shape 会被重复定义
#define SHAPEH

#include <iostream>
#include <algorithm>
#include <cmath>
#include <string>
using std::string;

class Shape{
protected:

public:
    Shape() {}
    virtual ~Shape(){}
    virtual double area() const = 0;
    virtual double perimeter() const = 0;
    virtual string name() const = 0;
    void print() const;
};

#endif

Shape.cpp

#include "Shape.h"
#include <iomanip>

void Shape::print() const {
    std::cout << "name: " << name() << '\n';
    std::cout << std::fixed << std::setprecision(2) << "area: " << area() << '\n';
    std::cout << std::fixed << std::setprecision(2) << "perimeter: " << perimeter() << '\n';
}

Square.h

#ifndef SQUARE_H
#define SQUARE_H

#include "Shape.h"

class Square : public Shape{
private:
    double b;
public:
    Square(): b(1.0) {}
    Square(double b_) : b(b_) {}
    ~Square() {}
    double area() const override;
    double perimeter() const override;
    string name() const override;
    double get_b() const ;
};

#endif

Square.cpp

#include "Square.h"

double Square::area() const {
    return b*b;
}

double Square::perimeter() const {
    return 4*b;
}

string Square::name() const {
    return "Square";
}

double Square::get_b() const{
    return b;
}

Triangle.h

#ifndef TRIANGLE_H
#define TRIANGLE_H

#include "Shape.h"
#include <algorithm>

class Triangle : public Shape{
private:
    double a;
    double b;
    double c;
public:
    Triangle(): a(5.0) , b(4.0) , c(3.0) {}
    Triangle(double a_, double b_, double c_);
    ~Triangle() {}
    double area() const override;
    double perimeter() const override;
    string name() const override;
    double get_a() const ;
    double get_b() const;
    double get_c() const;
};

#endif

Triangle.cpp

#include "Triangle.h"
#include <algorithm>

Triangle::Triangle(double a_, double b_, double c_){
    double tmp[3] = {a_, b_, c_};
    // 疑似反直觉:海伦公式不要求排序;去掉后 area/perimeter 等价,但 get_a/get_b/get_c 返回输入顺序,是否有调用方依赖 a<=b<=c 需验证
    std::sort(tmp, tmp + 3);
    a = tmp[0];
    b = tmp[1];
    c = tmp[2];
}

double Triangle::area() const {
    double p = perimeter()/2.0;
    return sqrt(p*(p-a)*(p-b)*(p-c));
}

double Triangle::perimeter() const {
    return a+b+c;
}

string Triangle::name() const {
    return "Triangle";
}

double Triangle::get_a() const{
    return a;
}

double Triangle::get_b() const{
    return b;
}

double Triangle::get_c() const{
    return c;
}

Circle.h

#ifndef CIRCLE_H
#define CIRCLE_H

#include "Shape.h"

class Circle : public Shape{
private:
    double r;
public:
    Circle(): Shape(), r(1.0) {}
    Circle(double r_) : Shape(), r(r_) {}
    ~Circle() {}
    double area() const override;
    double perimeter() const override;
    string name() const override;
    double get_r() const ;
    double get_d() const;
};

#endif

Circle.cpp

#include "Circle.h"

double Circle::area() const {
    return 3.14159 * r * r;
}

double Circle::perimeter() const {
    return 2*3.14159*r;
}

string Circle::name() const {
    return "Circle";
}

double Circle::get_r() const{
    return r;
}

double Circle::get_d() const{
    return r*2;
}

main.cpp

#include "Shape.h"
#include "Circle.h"
#include "Rectangle.h"
#include "Square.h"
#include "Triangle.h"
#include <vector>
#include <memory>
#include <iomanip>
#include <string>
#include <sstream>
using std::cout;
using std::cin;

// 创建图形、列出图形、计算总面积、计算总周长、删除图形和退出

std::vector<std::unique_ptr<Shape>> v;

bool ok(double a, double b, double c){
    if(a + b > c && a + c > b && b + c > a) return true;
    return false;
}

bool readint(int n, std::vector<int>& vs){
    string line;
    if(!getline(cin, line)) return false;

    std::stringstream ss(line);
    for(int i = 0; i < n; i++){
        int x;
        if(!(ss >> x)) return false;
        vs.push_back(x);
    }

    char extra;
    if(ss >> extra) return false;
    
    return true;
    
}

bool readdouble(int n, std::vector<double>& vs){
    string line;
    if(!getline(cin, line)) return false;

    std::stringstream ss(line);
    for(int i = 0; i < n; i++){
        // 不能声明为 int:输入 2.5 时只读到 2,随后 ss>>extra 读到 '.' 返回 false,小数无法创建
        int x;
        if(!(ss >> x)) return false;
        vs.push_back(x);
    }

    char extra;
    if(ss >> extra) return false;

    return true;
}

void create() {
    cout << "====================" << '\n';
    cout << "[1] circle" << '\n';
    cout << "[2] rectangle" << '\n';
    cout << "[3] square" << '\n';
    cout << "[4] triangle" << '\n';
    cout << "[0] quit" << '\n';
    cout << '\n';
    cout << "please cin" << '\n';
    cout << "====================" << '\n';
    int choice;
    std::vector<int> tmp;
    // readint 用 getline,而 showmain 的 cin>>choice 留下换行,首次 getline 会读到空行直接 return;需先 cin.ignore()
    if(!readint(1, tmp) || tmp.size() < 1) return;
    choice = tmp[0];
    switch(choice){
        case 1: {
            double r;
            std::vector<double> tm;
            if(!readdouble(1, tm) || tm.size() < 1) {cout << "please use number" << '\n'; break;}
            r = tm[0];
            if(r <= 0){
                cout << "error" << '\n';
                break;
            }
            std::unique_ptr<Shape> tmp = std::make_unique<Circle>(r);
            v.push_back(std::move(tmp));
        }break;
        case 2:{
            double l, s;
            std::vector<double> tm;
            if(!readdouble(2, tm) || tm.size() < 2) {cout << "please use number" << '\n'; break;}
            l = tm[0];
            s = tm[1];
            if(l <= 0 || s <= 0){
                cout << "error" << '\n';
                break;
            }
            std::unique_ptr<Shape> tmp = std::make_unique<Rectangle>(l, s);
            v.push_back(std::move(tmp));
        }break;
        case 3:{
            double b;
            std::vector<double> tm;
            if(!readdouble(1, tm) || tm.size() < 1) {cout << "please use number" << '\n'; break;}
            b = tm[0];
            if(b <= 0){
                cout << "error" << '\n';
                break;
            }
            std::unique_ptr<Shape> tmp = std::make_unique<Square>(b);
            v.push_back(std::move(tmp));
        }break;
        case 4:{
            double a, b, c;
            std::vector<double> tm;
            if(!readdouble(3, tm) || tm.size() < 3) {cout << "please use number" << '\n'; break;}
            a = tm[0];
            b = tm[1];
            c = tm[2];
            if(a <= 0 || b <= 0 || c <= 0 || !ok(a,b,c)){
                cout << "error" << '\n';
                break;
            }
            std::unique_ptr<Shape> tmp = std::make_unique<Triangle>(a, b, c);
            v.push_back(std::move(tmp));
        }break;
        default: {
            cout << "please use other numbers" << '\n';
        }
        case 0: break;
    }
    return;
}

void list() {
    if(v.size() == 0){
        cout << "empty" << '\n';
        return;
    }
    int index = 0;
    for(auto& x : v){
        cout << index++ << '\n';
        x->print();
    }
    return;
}

void getarrea() {
    if(v.size() == 0){
        cout << "empty" << '\n';
        return;
    }
    double res = 0.0;
    for(auto& x : v){
        res += x->area();
    }
    cout << std::fixed << std::setprecision(2) << res << '\n';
    return;
}

void getperimeter() {
    if(v.size() == 0){
        cout << "empty" << '\n';
        return;
    }
    double res = 0.0;
    for(auto& x : v){
        res += x->perimeter();
    }
    cout << std::fixed << std::setprecision(2) << res << '\n';
    return;
}

void deletes() {
    int index;
    cin >> index;
    
    if(v.size() == 0){
        cout << "empty" << '\n';
        return;
    }
    if(index >= v.size() || index < 0){
        cout << "out range" << '\n';
        return;
    }
    v.erase(v.begin() + index);
    return;
}

void quit() {
    cout << "byebye" << '\n';
    return;
}

void showmain(){
    while(1){
        cout << "====================" << '\n';
        cout << "[1] create a shape" << '\n';
        cout << "[2] list the shapes" << '\n';
        cout << "[3] get all area" << '\n';
        cout << "[4] get all perimeter" << '\n';
        cout << "[5] delete a shape" << '\n';
        cout << "[0] quit" << '\n';
        cout << '\n';
        cout << "please cin" << '\n';
        cout << "====================" << '\n';
    
        int choice;
        cin >> choice;
        switch(choice){
            case 1:create(); break;
            case 2:list();break;
            case 3:getarrea();break;
            case 4:getperimeter();break;
            case 5:deletes();break;
            case 0:quit();break;
            default:
                cout << "quit" << '\n';
                break;
        }
        if(choice == 0) break;
    }
}

int main(){
    using namespace std;
    ios::sync_with_stdio(false);
    cin.tie(nullptr);
    cout.tie(nullptr);
    showmain();
    return 0;
}








posted @ 2026-11-01 17:35  abnb250  阅读(2)  评论(0)    收藏  举报