完成
This commit is contained in:
@@ -1,24 +1,88 @@
|
||||
# 计算器项目
|
||||
|
||||
一个支持基本四则运算的命令行计算器程序。
|
||||
这是一个简单的命令行计算器项目,演示了 Go 语言的基本语法和编程概念的综合应用。
|
||||
|
||||
## 功能特性
|
||||
- 支持加法、减法、乘法、除法
|
||||
- 错误处理(如除零错误)
|
||||
## 项目特性
|
||||
|
||||
- 支持基本四则运算(加、减、乘、除)
|
||||
- 支持括号运算
|
||||
- 支持浮点数计算
|
||||
- 错误处理和输入验证
|
||||
- 交互式命令行界面
|
||||
- 输入验证
|
||||
- 历史记录功能
|
||||
|
||||
## 项目结构
|
||||
|
||||
```
|
||||
01-calculator/
|
||||
├── README.md # 项目说明文档
|
||||
├── main.go # 主程序入口
|
||||
├── calculator/ # 计算器核心包
|
||||
│ ├── calculator.go # 计算器主要逻辑
|
||||
│ ├── parser.go # 表达式解析器
|
||||
│ └── history.go # 历史记录管理
|
||||
└── calculator_test.go # 测试文件
|
||||
```
|
||||
|
||||
## 运行方法
|
||||
|
||||
```bash
|
||||
cd 01-calculator
|
||||
# 进入项目目录
|
||||
cd 10-projects/01-calculator
|
||||
|
||||
# 运行程序
|
||||
go run main.go
|
||||
|
||||
# 或者编译后运行
|
||||
go build -o calculator main.go
|
||||
./calculator
|
||||
```
|
||||
|
||||
## 使用示例
|
||||
|
||||
```
|
||||
欢迎使用 Go 计算器!
|
||||
请输入第一个数字: 10
|
||||
请选择运算符 (+, -, *, /): +
|
||||
请输入第二个数字: 5
|
||||
结果: 10 + 5 = 15
|
||||
```
|
||||
输入数学表达式,或输入 'quit' 退出,'history' 查看历史记录
|
||||
|
||||
> 2 + 3
|
||||
结果: 5
|
||||
|
||||
> 10 * (5 - 2)
|
||||
结果: 30
|
||||
|
||||
> 15 / 3
|
||||
结果: 5
|
||||
|
||||
> history
|
||||
历史记录:
|
||||
1. 2 + 3 = 5
|
||||
2. 10 * (5 - 2) = 30
|
||||
3. 15 / 3 = 5
|
||||
|
||||
> quit
|
||||
再见!
|
||||
```
|
||||
|
||||
## 学习要点
|
||||
|
||||
这个项目综合运用了以下 Go 语言特性:
|
||||
|
||||
1. **包管理**: 创建和使用自定义包
|
||||
2. **结构体和方法**: 定义计算器结构体和相关方法
|
||||
3. **接口**: 定义计算器接口,实现多态
|
||||
4. **错误处理**: 处理除零错误、语法错误等
|
||||
5. **字符串处理**: 解析和处理用户输入
|
||||
6. **切片操作**: 管理历史记录
|
||||
7. **控制流程**: 使用循环和条件语句
|
||||
8. **用户交互**: 命令行输入输出
|
||||
9. **测试**: 编写单元测试验证功能
|
||||
|
||||
## 扩展建议
|
||||
|
||||
1. 添加更多数学函数(sin, cos, sqrt 等)
|
||||
2. 支持变量定义和使用
|
||||
3. 添加配置文件支持
|
||||
4. 实现图形用户界面
|
||||
5. 添加科学计算功能
|
||||
6. 支持不同进制转换
|
||||
7. 添加单位换算功能
|
@@ -0,0 +1,290 @@
|
||||
/*
|
||||
calculator.go - 计算器核心逻辑
|
||||
实现了基本的四则运算和括号运算功能
|
||||
*/
|
||||
|
||||
package calculator
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// Calculator 接口定义了计算器的基本功能
|
||||
type Calculator interface {
|
||||
Calculate(expression string) (float64, error)
|
||||
GetHistory() []HistoryRecord
|
||||
ClearHistory()
|
||||
}
|
||||
|
||||
// BasicCalculator 基本计算器实现
|
||||
type BasicCalculator struct {
|
||||
history []HistoryRecord
|
||||
}
|
||||
|
||||
// NewCalculator 创建新的计算器实例
|
||||
func NewCalculator() Calculator {
|
||||
return &BasicCalculator{
|
||||
history: make([]HistoryRecord, 0),
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate 计算数学表达式
|
||||
func (c *BasicCalculator) Calculate(expression string) (float64, error) {
|
||||
// 清理输入
|
||||
expr := strings.ReplaceAll(expression, " ", "")
|
||||
if expr == "" {
|
||||
return 0, fmt.Errorf("表达式不能为空")
|
||||
}
|
||||
|
||||
// 验证表达式
|
||||
if err := c.validateExpression(expr); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 解析和计算
|
||||
result, err := c.evaluateExpression(expr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 添加到历史记录
|
||||
c.addToHistory(expression, result)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// GetHistory 获取计算历史
|
||||
func (c *BasicCalculator) GetHistory() []HistoryRecord {
|
||||
// 返回历史记录的副本
|
||||
history := make([]HistoryRecord, len(c.history))
|
||||
copy(history, c.history)
|
||||
return history
|
||||
}
|
||||
|
||||
// ClearHistory 清空历史记录
|
||||
func (c *BasicCalculator) ClearHistory() {
|
||||
c.history = make([]HistoryRecord, 0)
|
||||
}
|
||||
|
||||
// validateExpression 验证表达式的有效性
|
||||
func (c *BasicCalculator) validateExpression(expr string) error {
|
||||
if len(expr) == 0 {
|
||||
return fmt.Errorf("表达式不能为空")
|
||||
}
|
||||
|
||||
// 检查括号匹配
|
||||
parentheses := 0
|
||||
for _, char := range expr {
|
||||
switch char {
|
||||
case '(':
|
||||
parentheses++
|
||||
case ')':
|
||||
parentheses--
|
||||
if parentheses < 0 {
|
||||
return fmt.Errorf("括号不匹配")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if parentheses != 0 {
|
||||
return fmt.Errorf("括号不匹配")
|
||||
}
|
||||
|
||||
// 检查有效字符
|
||||
validChars := "0123456789+-*/.() "
|
||||
for _, char := range expr {
|
||||
if !strings.ContainsRune(validChars, char) {
|
||||
return fmt.Errorf("包含无效字符: %c", char)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查连续运算符
|
||||
operators := "+-*/"
|
||||
for i := 0; i < len(expr)-1; i++ {
|
||||
if strings.ContainsRune(operators, rune(expr[i])) &&
|
||||
strings.ContainsRune(operators, rune(expr[i+1])) {
|
||||
return fmt.Errorf("连续的运算符")
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// evaluateExpression 计算表达式的值
|
||||
func (c *BasicCalculator) evaluateExpression(expr string) (float64, error) {
|
||||
// 处理括号
|
||||
for strings.Contains(expr, "(") {
|
||||
// 找到最内层的括号
|
||||
start := -1
|
||||
for i, char := range expr {
|
||||
if char == '(' {
|
||||
start = i
|
||||
} else if char == ')' {
|
||||
if start == -1 {
|
||||
return 0, fmt.Errorf("括号不匹配")
|
||||
}
|
||||
|
||||
// 计算括号内的表达式
|
||||
subExpr := expr[start+1 : i]
|
||||
subResult, err := c.evaluateSimpleExpression(subExpr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 替换括号表达式为结果
|
||||
expr = expr[:start] + fmt.Sprintf("%g", subResult) + expr[i+1:]
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 计算简单表达式(无括号)
|
||||
return c.evaluateSimpleExpression(expr)
|
||||
}
|
||||
|
||||
// evaluateSimpleExpression 计算简单表达式(无括号)
|
||||
func (c *BasicCalculator) evaluateSimpleExpression(expr string) (float64, error) {
|
||||
if expr == "" {
|
||||
return 0, fmt.Errorf("空表达式")
|
||||
}
|
||||
|
||||
// 解析表达式为标记
|
||||
tokens, err := c.tokenize(expr)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
if len(tokens) == 0 {
|
||||
return 0, fmt.Errorf("空表达式")
|
||||
}
|
||||
|
||||
// 如果只有一个标记,直接返回数值
|
||||
if len(tokens) == 1 {
|
||||
return strconv.ParseFloat(tokens[0], 64)
|
||||
}
|
||||
|
||||
// 先处理乘法和除法
|
||||
for i := 1; i < len(tokens); i += 2 {
|
||||
if i >= len(tokens) {
|
||||
break
|
||||
}
|
||||
|
||||
operator := tokens[i]
|
||||
if operator == "*" || operator == "/" {
|
||||
left, err := strconv.ParseFloat(tokens[i-1], 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("无效的数字: %s", tokens[i-1])
|
||||
}
|
||||
|
||||
right, err := strconv.ParseFloat(tokens[i+1], 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("无效的数字: %s", tokens[i+1])
|
||||
}
|
||||
|
||||
var result float64
|
||||
if operator == "*" {
|
||||
result = left * right
|
||||
} else {
|
||||
if right == 0 {
|
||||
return 0, fmt.Errorf("除零错误")
|
||||
}
|
||||
result = left / right
|
||||
}
|
||||
|
||||
// 替换三个标记为结果
|
||||
newTokens := make([]string, 0, len(tokens)-2)
|
||||
newTokens = append(newTokens, tokens[:i-1]...)
|
||||
newTokens = append(newTokens, fmt.Sprintf("%g", result))
|
||||
newTokens = append(newTokens, tokens[i+2:]...)
|
||||
tokens = newTokens
|
||||
i -= 2 // 调整索引
|
||||
}
|
||||
}
|
||||
|
||||
// 再处理加法和减法
|
||||
result, err := strconv.ParseFloat(tokens[0], 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("无效的数字: %s", tokens[0])
|
||||
}
|
||||
|
||||
for i := 1; i < len(tokens); i += 2 {
|
||||
if i+1 >= len(tokens) {
|
||||
break
|
||||
}
|
||||
|
||||
operator := tokens[i]
|
||||
operand, err := strconv.ParseFloat(tokens[i+1], 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("无效的数字: %s", tokens[i+1])
|
||||
}
|
||||
|
||||
switch operator {
|
||||
case "+":
|
||||
result += operand
|
||||
case "-":
|
||||
result -= operand
|
||||
default:
|
||||
return 0, fmt.Errorf("未知的运算符: %s", operator)
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// tokenize 将表达式分解为标记
|
||||
func (c *BasicCalculator) tokenize(expr string) ([]string, error) {
|
||||
var tokens []string
|
||||
var current strings.Builder
|
||||
|
||||
for i, char := range expr {
|
||||
switch char {
|
||||
case '+', '-', '*', '/':
|
||||
// 处理负号
|
||||
if char == '-' && (i == 0 || expr[i-1] == '(' || strings.ContainsRune("+-*/", rune(expr[i-1]))) {
|
||||
current.WriteRune(char)
|
||||
continue
|
||||
}
|
||||
|
||||
// 保存当前数字
|
||||
if current.Len() > 0 {
|
||||
tokens = append(tokens, current.String())
|
||||
current.Reset()
|
||||
}
|
||||
|
||||
// 保存运算符
|
||||
tokens = append(tokens, string(char))
|
||||
|
||||
case ' ':
|
||||
// 忽略空格
|
||||
continue
|
||||
|
||||
default:
|
||||
// 数字或小数点
|
||||
current.WriteRune(char)
|
||||
}
|
||||
}
|
||||
|
||||
// 保存最后的数字
|
||||
if current.Len() > 0 {
|
||||
tokens = append(tokens, current.String())
|
||||
}
|
||||
|
||||
return tokens, nil
|
||||
}
|
||||
|
||||
// addToHistory 添加计算记录到历史
|
||||
func (c *BasicCalculator) addToHistory(expression string, result float64) {
|
||||
record := HistoryRecord{
|
||||
Expression: expression,
|
||||
Result: result,
|
||||
}
|
||||
c.history = append(c.history, record)
|
||||
|
||||
// 限制历史记录数量
|
||||
const maxHistory = 100
|
||||
if len(c.history) > maxHistory {
|
||||
c.history = c.history[len(c.history)-maxHistory:]
|
||||
}
|
||||
}
|
244
golang-learning/10-projects/01-calculator/calculator/history.go
Normal file
244
golang-learning/10-projects/01-calculator/calculator/history.go
Normal file
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
history.go - 历史记录管理
|
||||
定义了历史记录的数据结构和相关功能
|
||||
*/
|
||||
|
||||
package calculator
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
// HistoryRecord 表示一条计算历史记录
|
||||
type HistoryRecord struct {
|
||||
Expression string `json:"expression"` // 表达式
|
||||
Result float64 `json:"result"` // 计算结果
|
||||
Timestamp time.Time `json:"timestamp"` // 计算时间
|
||||
}
|
||||
|
||||
// NewHistoryRecord 创建新的历史记录
|
||||
func NewHistoryRecord(expression string, result float64) HistoryRecord {
|
||||
return HistoryRecord{
|
||||
Expression: expression,
|
||||
Result: result,
|
||||
Timestamp: time.Now(),
|
||||
}
|
||||
}
|
||||
|
||||
// String 返回历史记录的字符串表示
|
||||
func (h HistoryRecord) String() string {
|
||||
return fmt.Sprintf("%s = %g (计算时间: %s)",
|
||||
h.Expression,
|
||||
h.Result,
|
||||
h.Timestamp.Format("2006-01-02 15:04:05"))
|
||||
}
|
||||
|
||||
// HistoryManager 历史记录管理器
|
||||
type HistoryManager struct {
|
||||
records []HistoryRecord
|
||||
filePath string
|
||||
maxSize int
|
||||
}
|
||||
|
||||
// NewHistoryManager 创建新的历史记录管理器
|
||||
func NewHistoryManager(filePath string, maxSize int) *HistoryManager {
|
||||
return &HistoryManager{
|
||||
records: make([]HistoryRecord, 0),
|
||||
filePath: filePath,
|
||||
maxSize: maxSize,
|
||||
}
|
||||
}
|
||||
|
||||
// Add 添加历史记录
|
||||
func (hm *HistoryManager) Add(expression string, result float64) {
|
||||
record := NewHistoryRecord(expression, result)
|
||||
hm.records = append(hm.records, record)
|
||||
|
||||
// 限制历史记录数量
|
||||
if len(hm.records) > hm.maxSize {
|
||||
hm.records = hm.records[len(hm.records)-hm.maxSize:]
|
||||
}
|
||||
}
|
||||
|
||||
// GetAll 获取所有历史记录
|
||||
func (hm *HistoryManager) GetAll() []HistoryRecord {
|
||||
// 返回副本以防止外部修改
|
||||
records := make([]HistoryRecord, len(hm.records))
|
||||
copy(records, hm.records)
|
||||
return records
|
||||
}
|
||||
|
||||
// GetLast 获取最近的 n 条记录
|
||||
func (hm *HistoryManager) GetLast(n int) []HistoryRecord {
|
||||
if n <= 0 {
|
||||
return []HistoryRecord{}
|
||||
}
|
||||
|
||||
if n >= len(hm.records) {
|
||||
return hm.GetAll()
|
||||
}
|
||||
|
||||
start := len(hm.records) - n
|
||||
records := make([]HistoryRecord, n)
|
||||
copy(records, hm.records[start:])
|
||||
return records
|
||||
}
|
||||
|
||||
// Clear 清空历史记录
|
||||
func (hm *HistoryManager) Clear() {
|
||||
hm.records = make([]HistoryRecord, 0)
|
||||
}
|
||||
|
||||
// Count 获取历史记录数量
|
||||
func (hm *HistoryManager) Count() int {
|
||||
return len(hm.records)
|
||||
}
|
||||
|
||||
// SaveToFile 保存历史记录到文件
|
||||
func (hm *HistoryManager) SaveToFile() error {
|
||||
if hm.filePath == "" {
|
||||
return fmt.Errorf("文件路径未设置")
|
||||
}
|
||||
|
||||
data, err := json.MarshalIndent(hm.records, "", " ")
|
||||
if err != nil {
|
||||
return fmt.Errorf("序列化历史记录失败: %v", err)
|
||||
}
|
||||
|
||||
err = ioutil.WriteFile(hm.filePath, data, 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("写入文件失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadFromFile 从文件加载历史记录
|
||||
func (hm *HistoryManager) LoadFromFile() error {
|
||||
if hm.filePath == "" {
|
||||
return fmt.Errorf("文件路径未设置")
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
if _, err := os.Stat(hm.filePath); os.IsNotExist(err) {
|
||||
// 文件不存在,创建空的历史记录
|
||||
hm.records = make([]HistoryRecord, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
data, err := ioutil.ReadFile(hm.filePath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("读取文件失败: %v", err)
|
||||
}
|
||||
|
||||
err = json.Unmarshal(data, &hm.records)
|
||||
if err != nil {
|
||||
return fmt.Errorf("反序列化历史记录失败: %v", err)
|
||||
}
|
||||
|
||||
// 限制历史记录数量
|
||||
if len(hm.records) > hm.maxSize {
|
||||
hm.records = hm.records[len(hm.records)-hm.maxSize:]
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Search 搜索包含指定关键词的历史记录
|
||||
func (hm *HistoryManager) Search(keyword string) []HistoryRecord {
|
||||
var results []HistoryRecord
|
||||
|
||||
for _, record := range hm.records {
|
||||
if contains(record.Expression, keyword) {
|
||||
results = append(results, record)
|
||||
}
|
||||
}
|
||||
|
||||
return results
|
||||
}
|
||||
|
||||
// GetStatistics 获取历史记录统计信息
|
||||
func (hm *HistoryManager) GetStatistics() map[string]interface{} {
|
||||
stats := make(map[string]interface{})
|
||||
|
||||
stats["total_count"] = len(hm.records)
|
||||
|
||||
if len(hm.records) == 0 {
|
||||
return stats
|
||||
}
|
||||
|
||||
// 统计运算符使用频率
|
||||
operatorCount := make(map[string]int)
|
||||
for _, record := range hm.records {
|
||||
for _, char := range record.Expression {
|
||||
switch char {
|
||||
case '+':
|
||||
operatorCount["addition"]++
|
||||
case '-':
|
||||
operatorCount["subtraction"]++
|
||||
case '*':
|
||||
operatorCount["multiplication"]++
|
||||
case '/':
|
||||
operatorCount["division"]++
|
||||
}
|
||||
}
|
||||
}
|
||||
stats["operator_usage"] = operatorCount
|
||||
|
||||
// 最早和最晚的计算时间
|
||||
if len(hm.records) > 0 {
|
||||
earliest := hm.records[0].Timestamp
|
||||
latest := hm.records[0].Timestamp
|
||||
|
||||
for _, record := range hm.records {
|
||||
if record.Timestamp.Before(earliest) {
|
||||
earliest = record.Timestamp
|
||||
}
|
||||
if record.Timestamp.After(latest) {
|
||||
latest = record.Timestamp
|
||||
}
|
||||
}
|
||||
|
||||
stats["earliest_calculation"] = earliest.Format("2006-01-02 15:04:05")
|
||||
stats["latest_calculation"] = latest.Format("2006-01-02 15:04:05")
|
||||
}
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// ExportToCSV 导出历史记录为 CSV 格式
|
||||
func (hm *HistoryManager) ExportToCSV(filePath string) error {
|
||||
var csvContent strings.Builder
|
||||
|
||||
// CSV 头部
|
||||
csvContent.WriteString("Expression,Result,Timestamp\n")
|
||||
|
||||
// 数据行
|
||||
for _, record := range hm.records {
|
||||
csvContent.WriteString(fmt.Sprintf("\"%s\",%g,\"%s\"\n",
|
||||
record.Expression,
|
||||
record.Result,
|
||||
record.Timestamp.Format("2006-01-02 15:04:05")))
|
||||
}
|
||||
|
||||
err := ioutil.WriteFile(filePath, []byte(csvContent.String()), 0644)
|
||||
if err != nil {
|
||||
return fmt.Errorf("导出 CSV 文件失败: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// contains 检查字符串是否包含子字符串(忽略大小写)
|
||||
func contains(s, substr string) bool {
|
||||
return len(s) >= len(substr) &&
|
||||
(substr == "" ||
|
||||
len(s) > 0 &&
|
||||
(s == substr ||
|
||||
strings.Contains(strings.ToLower(s), strings.ToLower(substr))))
|
||||
}
|
214
golang-learning/10-projects/01-calculator/calculator_test.go
Normal file
214
golang-learning/10-projects/01-calculator/calculator_test.go
Normal file
@@ -0,0 +1,214 @@
|
||||
/*
|
||||
calculator_test.go - 计算器测试文件
|
||||
测试计算器的各种功能
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"./calculator"
|
||||
)
|
||||
|
||||
// TestBasicOperations 测试基本运算
|
||||
func TestBasicOperations(t *testing.T) {
|
||||
calc := calculator.NewCalculator()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expression string
|
||||
expected float64
|
||||
shouldErr bool
|
||||
}{
|
||||
{"加法", "2 + 3", 5, false},
|
||||
{"减法", "10 - 4", 6, false},
|
||||
{"乘法", "3 * 7", 21, false},
|
||||
{"除法", "15 / 3", 5, false},
|
||||
{"浮点数加法", "2.5 + 1.5", 4, false},
|
||||
{"浮点数除法", "7.5 / 2.5", 3, false},
|
||||
{"负数", "-5 + 3", -2, false},
|
||||
{"除零错误", "5 / 0", 0, true},
|
||||
{"空表达式", "", 0, true},
|
||||
{"无效字符", "2 + a", 0, true},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := calc.Calculate(tt.expression)
|
||||
|
||||
if tt.shouldErr {
|
||||
if err == nil {
|
||||
t.Errorf("期望出现错误,但没有错误")
|
||||
}
|
||||
} else {
|
||||
if err != nil {
|
||||
t.Errorf("不期望出现错误,但出现了错误: %v", err)
|
||||
}
|
||||
if result != tt.expected {
|
||||
t.Errorf("期望结果 %g,实际结果 %g", tt.expected, result)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestComplexExpressions 测试复杂表达式
|
||||
func TestComplexExpressions(t *testing.T) {
|
||||
calc := calculator.NewCalculator()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
expression string
|
||||
expected float64
|
||||
}{
|
||||
{"括号运算", "(2 + 3) * 4", 20},
|
||||
{"嵌套括号", "((2 + 3) * 4) - 5", 15},
|
||||
{"运算优先级", "2 + 3 * 4", 14},
|
||||
{"复杂表达式", "10 + (5 - 2) * 3", 19},
|
||||
{"多层嵌套", "(2 + (3 * 4)) / 2", 7},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result, err := calc.Calculate(tt.expression)
|
||||
if err != nil {
|
||||
t.Errorf("不期望出现错误: %v", err)
|
||||
}
|
||||
if result != tt.expected {
|
||||
t.Errorf("期望结果 %g,实际结果 %g", tt.expected, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// TestHistory 测试历史记录功能
|
||||
func TestHistory(t *testing.T) {
|
||||
calc := calculator.NewCalculator()
|
||||
|
||||
// 执行一些计算
|
||||
expressions := []string{"2 + 3", "10 - 4", "3 * 7"}
|
||||
for _, expr := range expressions {
|
||||
_, err := calc.Calculate(expr)
|
||||
if err != nil {
|
||||
t.Errorf("计算 %s 时出现错误: %v", expr, err)
|
||||
}
|
||||
}
|
||||
|
||||
// 检查历史记录
|
||||
history := calc.GetHistory()
|
||||
if len(history) != len(expressions) {
|
||||
t.Errorf("期望历史记录数量 %d,实际数量 %d", len(expressions), len(history))
|
||||
}
|
||||
|
||||
// 验证历史记录内容
|
||||
for i, record := range history {
|
||||
if record.Expression != expressions[i] {
|
||||
t.Errorf("历史记录 %d 表达式不匹配,期望 %s,实际 %s",
|
||||
i, expressions[i], record.Expression)
|
||||
}
|
||||
}
|
||||
|
||||
// 测试清空历史记录
|
||||
calc.ClearHistory()
|
||||
history = calc.GetHistory()
|
||||
if len(history) != 0 {
|
||||
t.Errorf("清空后期望历史记录数量 0,实际数量 %d", len(history))
|
||||
}
|
||||
}
|
||||
|
||||
// TestErrorHandling 测试错误处理
|
||||
func TestErrorHandling(t *testing.T) {
|
||||
calc := calculator.NewCalculator()
|
||||
|
||||
errorTests := []struct {
|
||||
name string
|
||||
expression string
|
||||
errorMsg string
|
||||
}{
|
||||
{"括号不匹配1", "(2 + 3", "括号不匹配"},
|
||||
{"括号不匹配2", "2 + 3)", "括号不匹配"},
|
||||
{"连续运算符", "2 ++ 3", "连续的运算符"},
|
||||
{"除零", "5 / 0", "除零错误"},
|
||||
{"无效字符", "2 + @", "包含无效字符"},
|
||||
{"空表达式", "", "表达式不能为空"},
|
||||
}
|
||||
|
||||
for _, tt := range errorTests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, err := calc.Calculate(tt.expression)
|
||||
if err == nil {
|
||||
t.Errorf("期望出现错误,但没有错误")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkCalculate 基准测试
|
||||
func BenchmarkCalculate(b *testing.B) {
|
||||
calc := calculator.NewCalculator()
|
||||
expression := "(2 + 3) * 4 - 1"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := calc.Calculate(expression)
|
||||
if err != nil {
|
||||
b.Errorf("计算出现错误: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkSimpleAddition 简单加法基准测试
|
||||
func BenchmarkSimpleAddition(b *testing.B) {
|
||||
calc := calculator.NewCalculator()
|
||||
expression := "2 + 3"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := calc.Calculate(expression)
|
||||
if err != nil {
|
||||
b.Errorf("计算出现错误: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BenchmarkComplexExpression 复杂表达式基准测试
|
||||
func BenchmarkComplexExpression(b *testing.B) {
|
||||
calc := calculator.NewCalculator()
|
||||
expression := "((2 + 3) * 4 - 1) / (5 + 2)"
|
||||
|
||||
b.ResetTimer()
|
||||
for i := 0; i < b.N; i++ {
|
||||
_, err := calc.Calculate(expression)
|
||||
if err != nil {
|
||||
b.Errorf("计算出现错误: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ExampleCalculator_Calculate 计算器使用示例
|
||||
func ExampleCalculator_Calculate() {
|
||||
calc := calculator.NewCalculator()
|
||||
|
||||
result, err := calc.Calculate("2 + 3")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%.0f", result)
|
||||
// Output: 5
|
||||
}
|
||||
|
||||
// ExampleCalculator_ComplexExpression 复杂表达式示例
|
||||
func ExampleCalculator_ComplexExpression() {
|
||||
calc := calculator.NewCalculator()
|
||||
|
||||
result, err := calc.Calculate("(2 + 3) * 4")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Printf("%.0f", result)
|
||||
// Output: 20
|
||||
}
|
133
golang-learning/10-projects/01-calculator/main.go
Normal file
133
golang-learning/10-projects/01-calculator/main.go
Normal file
@@ -0,0 +1,133 @@
|
||||
/*
|
||||
main.go - 计算器项目主程序
|
||||
这是一个简单的命令行计算器,演示了 Go 语言的综合应用
|
||||
*/
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
"./calculator"
|
||||
)
|
||||
|
||||
func main() {
|
||||
// 创建计算器实例
|
||||
calc := calculator.NewCalculator()
|
||||
|
||||
// 创建输入扫描器
|
||||
scanner := bufio.NewScanner(os.Stdin)
|
||||
|
||||
// 显示欢迎信息
|
||||
printWelcome()
|
||||
|
||||
// 主循环
|
||||
for {
|
||||
fmt.Print("> ")
|
||||
|
||||
// 读取用户输入
|
||||
if !scanner.Scan() {
|
||||
break
|
||||
}
|
||||
|
||||
input := strings.TrimSpace(scanner.Text())
|
||||
|
||||
// 处理特殊命令
|
||||
switch strings.ToLower(input) {
|
||||
case "quit", "exit", "q":
|
||||
fmt.Println("再见!")
|
||||
return
|
||||
case "help", "h":
|
||||
printHelp()
|
||||
continue
|
||||
case "history":
|
||||
printHistory(calc)
|
||||
continue
|
||||
case "clear":
|
||||
calc.ClearHistory()
|
||||
fmt.Println("历史记录已清空")
|
||||
continue
|
||||
case "":
|
||||
continue
|
||||
}
|
||||
|
||||
// 计算表达式
|
||||
result, err := calc.Calculate(input)
|
||||
if err != nil {
|
||||
fmt.Printf("错误: %v\n", err)
|
||||
continue
|
||||
}
|
||||
|
||||
// 显示结果
|
||||
fmt.Printf("结果: %g\n", result)
|
||||
}
|
||||
|
||||
// 检查扫描器错误
|
||||
if err := scanner.Err(); err != nil {
|
||||
fmt.Printf("读取输入时发生错误: %v\n", err)
|
||||
}
|
||||
}
|
||||
|
||||
// printWelcome 显示欢迎信息
|
||||
func printWelcome() {
|
||||
fmt.Println("=== 欢迎使用 Go 计算器!===")
|
||||
fmt.Println()
|
||||
fmt.Println("支持的操作:")
|
||||
fmt.Println(" • 基本运算: +, -, *, /")
|
||||
fmt.Println(" • 括号运算: ( )")
|
||||
fmt.Println(" • 浮点数计算")
|
||||
fmt.Println()
|
||||
fmt.Println("特殊命令:")
|
||||
fmt.Println(" • help - 显示帮助信息")
|
||||
fmt.Println(" • history - 查看计算历史")
|
||||
fmt.Println(" • clear - 清空历史记录")
|
||||
fmt.Println(" • quit - 退出程序")
|
||||
fmt.Println()
|
||||
fmt.Println("请输入数学表达式:")
|
||||
}
|
||||
|
||||
// printHelp 显示帮助信息
|
||||
func printHelp() {
|
||||
fmt.Println()
|
||||
fmt.Println("=== 帮助信息 ===")
|
||||
fmt.Println()
|
||||
fmt.Println("支持的运算符:")
|
||||
fmt.Println(" + 加法 例: 2 + 3")
|
||||
fmt.Println(" - 减法 例: 5 - 2")
|
||||
fmt.Println(" * 乘法 例: 4 * 6")
|
||||
fmt.Println(" / 除法 例: 8 / 2")
|
||||
fmt.Println(" () 括号 例: (2 + 3) * 4")
|
||||
fmt.Println()
|
||||
fmt.Println("使用示例:")
|
||||
fmt.Println(" 2 + 3")
|
||||
fmt.Println(" 10 * (5 - 2)")
|
||||
fmt.Println(" 15.5 / 3.1")
|
||||
fmt.Println(" ((2 + 3) * 4) - 1")
|
||||
fmt.Println()
|
||||
fmt.Println("特殊命令:")
|
||||
fmt.Println(" help - 显示此帮助信息")
|
||||
fmt.Println(" history - 查看计算历史")
|
||||
fmt.Println(" clear - 清空历史记录")
|
||||
fmt.Println(" quit - 退出程序")
|
||||
fmt.Println()
|
||||
}
|
||||
|
||||
// printHistory 显示计算历史
|
||||
func printHistory(calc calculator.Calculator) {
|
||||
history := calc.GetHistory()
|
||||
|
||||
if len(history) == 0 {
|
||||
fmt.Println("暂无计算历史")
|
||||
return
|
||||
}
|
||||
|
||||
fmt.Println()
|
||||
fmt.Println("=== 计算历史 ===")
|
||||
for i, record := range history {
|
||||
fmt.Printf("%d. %s = %g\n", i+1, record.Expression, record.Result)
|
||||
}
|
||||
fmt.Println()
|
||||
}
|
Reference in New Issue
Block a user