C/C++类:数据保护与有理数运算计算器

before|正文之前:

c++实验代码及学习笔记(六)

你好! 这是一个高程实验课的代码记录及学习笔记。我将记录一些重要的知识点、易错点。但是作为大学生,水平很低,敬请指点教导、优化代码。

1问题

C/C++类:数据保护与有理数运算计算器_第1张图片
本实验紧接上回。本周我们学习了【数据保护与共享】,关于静态成员数据、常成员数据、常成员函数、常对象等等知识。所以为应用所学知识,我们要解决第一个问题:改进我们的有理数类。同时,学习运算符重载并改进。
第一题非常简单,选做题么,单看题目非常简单,只需支持加减法,不用考虑运算优先级。输入字符串,那我们就把字符串一个一个读下来,把分数部分创建有理数类,然后进行计算。看起来非常简单啊!
然而……
能让你怀疑人生啊!!

2思路

1数据共享与保护

静态数据成员

静态变量的好处是可以取代全局变量,它实际上是类域中的全局变量。所以静态数据成员的定义(初始化)不应该被放在头文件中,因为这样做会引起重复定义这样的错误。
参考文章:C++ 静态数据成员和静态成员函数
原理详见:(数据存储)C++类中的静态成员变量与静态成员函数
大概是这么个意思:程序要向内存爸爸申请内存,把不同的数据宝宝送到不同的内存幼儿园。内存分成四个幼儿园:堆区、栈区、代码区、全局数据区幼儿园,new产生的好动的动态数据宝宝去堆区、函数内产生的自动变量宝宝去栈区,函数over后自动变量就退出栈区了。而文静的静态数据宝宝则在最安全的全局变量区。
对于面向对象的static关键字,我们可以创建类的静态数据成员,在类中起作用。
调用静态函数:直接用类名,MyClass::function();

举例

//example 1
#include 

class MyClass{
public:
     MyClass(int a,int b,int c);
     void GetSum();

private:
    int a,b,c;
    static int sum;//声明静态数据成员
};

int MyClass::sum=0;//定义并初始化静态数据成员

需要注意的是,虽然在类内声明,但静态数据成员的初始化要在类外,用形如 int ClassName::sum=0;这样的形式。(VS6以上不支持在类内定义)

常成员变量/函数

常成员变量/函数在形式上的特征是加了 const 这个关键字
主要是程序员希望这个变量/函数不要轻易被改变,使用起来更方便等等。
参考文章:C++ const用法 尽可能使用const (const修饰指针变量、函数参数、成员函数、函数返回值)

C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助。

const修饰成员函数不能修改任何成员变量,且不能调用任何非const成员函数。
const修饰函数参数,则该参数传入后不可被修改。

2运算符重载

这其实就是上述静态、常成员变量/函数的一种应用
参考文章:C++运算符重载基础教程
C++实现有理数类加减乘除

关于运算符重载,教程说明的非常详细,仔细阅读,其实上手写一遍就会。
其实质是函数重载,如用类的数据类型定义,则该函数运算符重载只对该类有效。
给出重载+的示例,可仿照写出- * \ 的函数

public:
	Rational operator+(const Rational &r2) const
	{
		Rational r;
		r.num = this->num*r2.den + r2.num*this->den;
		r.den = this->den*r2.den;
		r.simple();
		return r;
	}

3设计有理数计算器

这道题乍一看并不难,输入字符串,转换为字符型/整型,然后依次读入,因为不要求括号、乘除,不涉及优先级运算,所以本题无需使用堆栈或递归。使用if判断识别,将分数储存为有理数类,再进行加减运算,最后使用有理数类的函数输出。
但是,在设计函数、具体实现的时候,只会面向百度编程的二狗遭遇了前所未有的困难。
所以这道题建议大家好好思考,再来阅读本篇回答。

这道题请了场外援助皮皮,皮皮采用暴力拆解的思想,是实现本题的简单方法。而面向百度编程的二狗坚持认为其中会用到递归或者堆栈,双方产生了极大的矛盾。但是编程能力极弱的菜狗并没能实现她的思想,在暴力拆解的方法上也遇到问题。
比如

  • 怎么将分数划分并储存到有理数类中?函数设计?
  • 怎么识别储存十位以上分子分母的分数?(如果是个位数只需/号-1,+1)
  • 循环体设计?用不用使用break?

最终皮皮来为我们解答:

  1. 首先创建一个字符串变量,键盘输入,并转为字符型
int main(){
	...
	string str;
	cout << "请输入一个分数运算表达式,支持加减。" << endl;
	cin >> str;

	int n = str.length();
	char *cal = new char[n];
	strcpy(cal, str.c_str());//.c_str()返回一个临时指针 
						//注意!VS10以上不支持strcpy函数,不安全,有两种解决方法
	...
	}
  1. 识别分数,跑到’+’ ‘-’和最后一位字符’\0’的时候将前面的分数创建为有理数r
    传u、v两个参数,整个表达式在数组里,u和v就是分数开始和结束的位置。
    截取形如 3/2 这样的分数。
	Rational r1;
	Rational r;
	int u = 0, v = 0;

	for (int i = 0; i <= n; i++)
	{
			if (i == n||cal[i] == '+' || cal[i] == '-')
			{
				v = i;
				r1 = getRational(u, v, cal);
				r = r + r1;		//每次的r1都不同,创建有理数r代表sum,储存运算结果。
				u = i + 1;
			}
	}
  1. 识别分子分母,跑到‘/’的时候将分数斜杠前的数存为分子,后面的数存为分母
    设计上面的getRational函数,传入uv两个参数和数组。Rational函数内调用一个获取分子分母的函数,传入uv和数组。
Rational getRational(int u, int v,char *cal)//u:被截取的字符串开头 
											//v:被截取的字符串末尾 形如2/3
{
	Rational r1;
	for (int i = u; i < v; i++)
	{
		if (cal[i] == '/')
		{
			r1.set(getNum(u, i, cal),getNum(i + 1, v ,cal));
		}
	}
	return r1;
}
  1. 获取分数分子分母的数组,读取,识别"/","/“前数字存为分子,”/"后存为分母
int getNum(int u,int v,char *cal)
{
	int ans = 0;
	for (int i = u; i < v; i++)
	{
		ans = ans * 10;		//进位
		ans = ans+cal[i]-'0';//字符型不是整型,要存数字,需要减'0'
	}
	return ans;
  1. 循环体补完。如果第一个是负数怎么办?——判断正负,再加减

代码实现

#include
#include
#include
using namespace std;

#pragma warning(disable:4996)

class Rational
{
	static int Objcount;
private:
	int num, den;//分子,分母

public:
	Rational(int n = 0, int m = 1):num(n),den(m)//初始分子分母 默认构造函数
	{
		//cout << "Count" << endl;
		Objcount++;
	}

	~Rational()
	{
		//cout << "Discount" << endl;
		Objcount--;
	}


	void set(int x, int y)//设定分子分母
	{
		num = x;
		den = y;
	}

	void print() const//输出x/y
	{
			cout << num << "/" << den << endl;
	}


	void simple()//化简
	{
		int temp;
		int x = num, y = den;
		while (temp = x % y)//辗转相除法
		{
			x = y;
			y = temp;
		}
		den /= y;
		num /= y;
		if (den < 0) {
			den = -den;
			num = -num;
		}
	}

public:
	Rational operator+(const Rational &r2) const
	{
		Rational r;
		r.num = this->num*r2.den + r2.num*this->den;
		r.den = this->den*r2.den;
		r.simple();
		return r;
	}

	Rational operator-(const Rational &r2) const
	{
		Rational r;
		r.num = this->num*r2.den - r2.num*this->den;
		r.den = this->den*r2.den;
		r.simple();
		return r;
	}

	Rational operator*(const Rational &r2) const
	{
		Rational r;
		r.num = this->num*r2.num;
		r.den = this->den*r2.den;
		r.simple();
		return r;
	}

	Rational operator/(const Rational &r2) const
	{
		Rational r;
		if (r2.num == 0 || r2.den == 0)
		{
			cout << "分母不得为0" << endl;
		}
		else {
			r.num = this->num*r2.den;
			r.den = r2.num*this->den;
		}
		r.simple();
		return r;
	}
};

int Rational::Objcount = 0;

int getNum(int u,int v,char *cal)
{
	int ans = 0;
	for (int i = u; i < v; i++)
	{
		ans = ans * 10;
		ans = ans+cal[i]-'0';
	}
	return ans;
}

Rational getRational(int u, int v,char *cal)//u:被截取的字符串开头 v:被截取的字符串末尾 形如2/3
{
	Rational r1;
	for (int i = u; i < v; i++)
	{
		if (cal[i] == '/')
		{
			r1.set(getNum(u, i, cal),getNum(i + 1, v ,cal));
		}
	}
	return r1;
}


int main()
{
	Rational r;
	Rational r1;

	string str;
	cout << "请输入一个分数运算表达式,支持加减。" << endl;
	cin >> str;

	int n = str.length();
	char *cal = new char[n];
	strcpy(cal, str.c_str());//.c_str()返回一个临时指针

	bool index = true;
	if (cal[0] == '-')
			index = false;
	int u = 0, v = 0;

	for (int i = 0; i <= n; i++)
	{
			if (i == n||cal[i] == '+' || cal[i] == '-')
			{
				v = i;
				r1 = getRational(u, v, cal);
				if (index == true) {
					r = r + r1;
				}
				else  r = r - r1;
				index = ((cal[i] == '+')?true : false);
				u = i + 1;
			}
	}
	
	cout << "运算结果为:" << endl;
	r.print();
	
	getchar();
	getchar();
	return 0;
}


4最终效果

C/C++类:数据保护与有理数运算计算器_第2张图片

5补充知识

strcpy规范

vs准备弃用strcpy的,安全性较低,所以微软提供了strcpy_s来代替,如果想继续使用strcpy的,main前面加上

#pragma warning(disable:4996)

strcpy_s是安全用法,要指定拷贝的长度

strcpy_s(p, 20, "hello world");

char与string间的相互转换

参考文章:c++ 中 char 与 string 之间的相互转换问题

  1. char转string
    直接赋值,转换
  2. string转char
    -调用string的data函数
    -调用string的c_str()函数
    -调用string的copy函数

最后,感觉我这个非常小白友好了,写这么详细真的不容易嗷,感谢大家阅读,鞠躬下台!

你可能感兴趣的:(学习笔记,c/c++)