(C++)简单计算器开发笔记(1)

1.原理介绍

利用C++编译一个简单的计算器程序想必是大家的一个小小的心愿吧。但怎样让程序能够识别用户输入的算是呢?这就需要我们巧妙地创建函数来实现我们的梦想!由于在数学的算式中乘除的优先于加减运算,所以我们必须要把两个相邻的加或减之间的数字或者乘除算式看成一个项,最后再对所有项(term)进行加减操作。为了简单化,这里不考虑计算负数,如果有兴趣你可以试试哦 :)

2.函数实现

我们总共需要4个主要的函数(其他的函数稍后介绍)

删除表达式中的空格:void eatspaces(char* str)对项进项操作的函数:dobule calc(char* str)计算项的值的函数:double getterm(char* str, int& index)读取数字的函数:double getnumber(char* str, int& index)

注意: str为用户输入的算式, index是str的索引。index在每个函数之间传递时使用按值传递方式在getterm函数体中将会把index递增到下一个操作符op2位置在getnumber函数体中会把index递增到下一个操作符op1位置

3.声明必要的信息

#include "stdafx.h"
#include 
#include //标准流
#include 
#include 

#include //用于数学函数

using std::endl;
using std::cout;
using std::cin;
using namespace std;
const int  MAX(100); //定义最大的算式长度


void eatspaces(char*);//申明删除空格的函数
double calc(char* str);//计算表达式
double getnumber(char* str, int& index);//用于获得数字
double getterm(char* str, int& index);//获取项值 类似于1*96  6/6的形式
char* getkuohao(char* str, int& index);//用于解析字符串的括号 该函数将会删除首尾括号并计算表达式的值
4.主函数和获取用户输入的算式

int main()
{
	char expression[MAX] = {0};
	//输出信息
	cout << "Win32计算器项目练习\n版权所有(C)2016" << endl;
	cout <<
		"支持小数点\n"
		"加(+)减(-)乘(*)除(/) 指数(^)\n"
		"支持三角函数sin 、cos 、tan(弧度制)sind、cosd、tand(角度值) 以及平方根sqrt 例如sind(30) sqrt(2+2)\n"
		"不支持负数运算\n"
		"注意: 输入错的表达式将会是程序退出.\n"
		"如果需要退出, 可以使用回车退出\n"
		"仅支持"<


5.删除空格函数eatspaces(char* str)

我们将会利用索引的差值,把空格位置的字符用下一个非空的字符“填充”

void eatspaces(char* exp)
{
	int i(0);//设置第一个元素的索引
	int j(0);
	while (*(exp+i) != '\0') //判断是否到达最后一位
	{
		*(exp + i) = *(exp + j++); //复制字符
		if (*(exp + i) != ' ')
		{
			i++;//使i 递增
		}
	}
	return;
}


5.对项进项操作的函数:dobule calc(char* str,int& index)

double calc(char* str)
{
	double value(0.0);
	int index = 0;
	value = getterm(str, index);//获得当前的项
	for (;;)//无线循环
	{
		switch (*(str+ index++))
		{
		case '+':
			value += getterm(str, index);//
			break;
		case '-':
			value -= getterm(str, index);
			break;
		case '\0':
			return value;//返回值
		default:
			exit(1);//出错了
			break;
		}
	}

}


调用getterm将会返回从当前操作符到笑=下一个操作符op2之间的项的值,并且会把index递增到下一个操作符的索引位置

5.计算项的值的函数:dobule getterm(char* str,int& index)

double getterm(char* str, int& index)
{
	double value(0.0);
	value = getnumber(str, index);//获取数组 并递增到下个运算符的位置
	while (true)
	{
		if (*(str + index) == '*')
		{
			value *= getnumber(str, ++index);//是否为乘法运算
		}
		else
		{
			if (*(str + index) == '/')
			{
				value /= getnumber(str, ++index);//是否为除法运算符
			}
			else
			{
				if (*(str + index) == '^')
				{
					value =pow(value, getnumber(str, ++index));//是否为幂运算符
				}
				else
				{
					break;
				}
			}
		}

	}
	return value;
}


调用getnumber将会返回从当前操作符到下一个操作符op1之间的数字的值,并且会把index递增到下一个操作符op1的索引位置

6.读取数字函数:dobule getnumber(char* str,int& index)

double getnumber(char* str, int& index)
{
	double value(0.0);
	char opname[6];//定义操作符的大小 数学的操作符一般最多为5个字符
	int ip = 0; //定义一个计数器
	while (isalpha(*(str+index)))
	{
		opname[ip++] = *(str + index++);
	}
	opname[ip] = '\0';//添加结尾
	if (opname[0] != '\0')
	{
		char* numberexp = getkuohao(str, ++index);//获取括号内容
		value = calc(numberexp);//计算括号内容
		value = calcoperator(opname, value);//计算函数值
		delete[] numberexp;//释放指针
		return value;//返回值
	}
	if (*(str + index) == '(') //计算括号内容
	{
		char* psubstr(nullptr);            // 设置指针
		psubstr = getkuohao(str, ++index);   // 获取括号内的内容
		value = calc(psubstr);             // 获取括号内的表达式
		delete[]psubstr;                   // 释放指针
		return value;                      // 返回值
	}
	if (isdigit(*(str + index))==false) 
	{ 
		exit(1); 
	}
	//判断是否为数字
	while (isdigit(*(str + index)))
	{
		value = value * 10 + (*(str + index++) - '0');//从左往右 计算数字
	}
	if (*(str + index) != '.') { return value; }//检查数字后是否包含小数点
	double fact = 1.0;//定义位数
	while (isdigit(*(str + (++index))))
	{
		fact = fact*0.1;
		value = value + (*(str + index) - '0')*fact;//自乘
	}
	return value;

}


你可能会看到一些其他的内容(稍后介绍),但是主体思路设这样的pic如何把字符类型9转换为数字9呢?例如:9所对应的ASCII码为39而0对应的ASCII码为30所以用 9对应的ASCII39减去0对应的ASCII30所得的就是数字9. 而char类型是可以进行操作的(实际上是对字符的ASCII码进行操作)如何把一串字符转换为可以计算的数字呢?函数返回的时候会把index递增到下一个操作符op1或op2的位置(如果是op1那么将会在本函数的调用位置进行op1操作,如果是op2那么getterm将会退出回到calc调用位置以表示该项计算完毕进行op2操作)

6.扩展功能 - 计算括号:dobule getkuohao(char* str,int& index)


char* getkuohao(char* str, int& index)
{
	char buffer[MAX];//声明一个局部数组
	char* pstr(nullptr);//设置一个 指针
	int numcount(0);//设置计数器 原理是 当发现一个左括号时+1 而当发现一个右括号时减去1 当再次检测到有括号时 如果值为0表示是最后一个括号 那么删除它 如果是1则表示他只是一个子括号
	int bufindex(index);//定义str的起始位置
	do
	{
		buffer[index - bufindex] = *(str + index);//局部数组第一个字符为第一个左括号的笑一个字符(调用前index自增)
		switch (buffer[index- bufindex])//判断该字符
		{
		case ')':
			if (0 == numcount)//到达最后一个右括号
			{
				buffer[index - bufindex] = '\0';//把这个字符用结尾\0替换
				++index;//递增index
				pstr = new char[index - bufindex];//在堆中创建一个字符串 
				if (!pstr)
				{
					cout << "内存分配故障" << endl;
					exit(1);
				}
				strcpy_s(pstr, index - bufindex, buffer);//复制字符串
				return pstr;//将创建的字符串以其地址返回
			}
			else
			{
				numcount--;//子右括号
				break;
			}
		case '(':
			numcount++;//左括号


			break;
		}
	} while (*(str + index++) != '\0');//直到最后一个字符
}

原理 当然了,删除掉第一个左括号和最后一个右括号之后我们会把返回的字符串当做一个新的算式进行计算(在getnumber函数体内调用calc函数)(在getnumber函数中)

if (*(str + index) == '(') //计算括号内容
	{
		char* psubstr(nullptr);            // 设置指针
		psubstr = getkuohao(str, ++index);   // 获取括号内的内容
		value = calc(psubstr);             // 获取括号内的表达式
		delete[]psubstr;                   // 释放指针
		return value;                      // 返回值
	}

7.扩展功能 - 计算数学的函数:double calcoperator(char* dooperator, double value)

double calcoperator(char* dooperator, double value)
{
	
	if (!_stricmp(dooperator, "sin"))//RAD SIN
		return sin(value);
	else if (!_stricmp(dooperator, "sind"))//DEG SIN
		return sin(value / degToRad);
	else if (!_stricmp(dooperator, "cos"))//RAD COS
		return cos(value);
	else if (!_stricmp(dooperator, "cosd"))//DEG COS
		return cos(value / degToRad);
	else if (!_stricmp(dooperator, "tan"))//RAD TAN
		return tan(value);
	else if (!_stricmp(dooperator, "tand"))//DEG TAN
		return tan(value / degToRad);
	else if (!_stricmp(dooperator, "sqrt"))//平方根
		return sqrt(value);
	else
	{
		//未知的函数
	}
	return 0.0;
}

如何判断算式中的数学函数?(在getnumber函数中)

char opname[6];//定义操作符的大小 数学的函数一般最多为5个字符
int ip = 0; //定义一个计数器
	while (isalpha(*(str+index)))
	{
		opname[ip++] = *(str + index++);
	}
	opname[ip] = '\0';//添加结尾
	if (opname[0] != '\0')
	{
		char* numberexp = getkuohao(str, ++index);//获取括号内容
		value = calc(numberexp);//计算括号内容
		value = calcoperator(opname, value);//计算函数值
		delete[] numberexp;//释放指针
		return value;//返回值
	}

我们可以读取从第一个字母到最后一个字母,其中得到的字符串就是数学函数,而且我们要求用户输入的数学函数面必须要带上()来限定计算的数字所以我们还需要得到括号内的内容。我们同样可以把括号的内容当做一个新的算式,然后把我们已经得到的函数名和需要计算的数字带到calcoperator函数中这样我们就可以得到函数的值了。

8.整体预览

// Wincalc.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"
#include 
#include //标准流
#include 
#include 

#include //用于数学函数

using std::endl;
using std::cout;
using std::cin;
using namespace std;
const int  MAX(100); //定义最大的表达式长度


void eatspaces(char*);//申明删除空格的函数
double calc(char* str);//计算表达式
double getnumber(char* str, int& index);//用于获得数字
double getterm(char* str, int& index);//获取项值 类似于1*96  6/6的形式
char* getkuohao(char* str, int& index);//用于解析字符串的括号 该函数将会删除首尾括号并计算表达式的值
double calcoperator(char* dooperator, double value);//用于计算各种运算符的函数
const double degToRad = 180.0 / 3.14159265358979323846; // 定义角度值转为弧度制的单位值

int main()
{
	char expression[MAX] = {0};
	//输出信息
	cout << "Win32计算器项目练习\n版权所有(C)2016" << endl;
	cout <<
		"支持小数点\n"
		"加(+)减(-)乘(*)除(/) 指数(^)\n"
		"支持三角函数sin 、cos 、tan(弧度制)sind、cosd、tand(角度值) 以及平方根sqrt 例如sind(30) sqrt(2+2)\n"
		"不支持负数运算\n"
		"注意: 输入错的表达式将会是程序退出.\n"
		"如果需要退出, 可以使用回车退出\n"
		"仅支持"<

 9.相关的函数

  • isdigit(char c) 需要引用cctype文件。该函数会检查参数c是否为数字,如果是将返回非0值(不为假)
  • isalpha(char ch) 需要引用cctype文件。该函数会检查参数ch是否为字母,如果是将返回1(真)
  • tan(double x)、sin(double x)、cos(double x) (x为弧度制)函数需要引用math.h头文件
  • tand(double x)、sind(double x)、cosd(double x) (x为角度制)函数需要引用math.h头文件
  • pow(double x, double y) 需要引用math.h头文件,函数等价于x的y次方
  • sqrt(double x)需要引用math.h头文件 等价于x的平方根
  • strcmp(char* s1, char* s2)用于比较s1和s2 如果s1=s2则返回0(假),注意不会区分大小写。
10.参考资料
  • isalpha函数
  • isdigit函数
  • strcmp函数
  • cmath库函数
  • 《Visual C++ 2010入门经典(第五版)》
11.代码下载 https://yunpan.cn/cPMY3CiDXRdEg 访问密码 3204   

你可能感兴趣的:(C++)