Complex类就不发出来了,相对于下面而言过于简单。
下面两个实验都做了扩展,并加入了一些自己的想法;
//!!!!!!!!重新写了Graph类(代码跟在旧的下面(hash_type没有变动)图片没有更新因为太麻烦了,想看实际效果的,还是自己运行吧)
提供一个贪吃蛇小游戏博客链接,可以从里面学到很多东西(就算不学习也可以用来消遣啊)
[https://blog.csdn.net/silence1772/article/details/55005008]
Graph类:
main主函数里面没有什么东西,主要都是针对用户体验的优化,还有就是两个系统指令,不过也是为了优化体验
main.cpp:
#include <iostream>
#include "graph.h"
using namespace std;
int main()
{
while(1)
{
system("CLS");
system("color 0F");//颜色复位
char m;
int n;
cout<<"请输入图形的构成字符与尺寸:"<<endl;
cin>>m>>n;
Graph graph(m,n);
graph.draw();
cout<<"你想要对当前图形进行更多操作吗?是(y),否(n)"<<endl;
char ch;
cin>>ch;
if(ch=='n')continue;
else if(ch=='y')
{
cout<<"您可以用方向键调整图形的位置(D),并可以设置图形的前景色和背景色(C)"<<endl
<<"您在操作中可以输入C或D以切换至相应的操作"<<endl
<<"当您需要结束对当前图形的操作时,请输入 B "<<endl;
while(1)
{
if(graph.coto!='B'&&graph.coto!='C'&&graph.coto!='D')cin>>graph.coto;
if(graph.coto=='B')break;
else if(graph.coto=='D')graph.movg();
else if(graph.coto=='C')graph.color();
else cout<<"请输入规定的字符,否则我们无法理解您的意图"<<endl;
}
}
graph.coto='0';
}
return 0;
}
graph头文件也没什么要说的,要解释的都已经注释了
graph.h:
#ifndef GRAPH_H
#define GRAPH_H
#include<cstdlib>
#include <conio.h>
#include <windows.h>
#include <string>
using namespace std;
// 类Graph的声明
class Graph {
public:
Graph(char ch, int n); // 带有参数的构造函数
void draw(); //绘制图形
void recolordraw(string a,string b); //重绘图形
void removgdraw(int* a);
void color(); //设置前景色与背景色
void movg(); //移动图形
char cdef(string a);
int m[2]={0,0};//记录待绘制图形相对于初始位置的移动,即图形状态参数
char coto='0';//用于记录用户对图形的主操作指令
char s[9]={'c','o','l','o','r',' ','0','F',' '};//字符串数组初始化,用于执行颜色设置
private:
char symbol;
int size;
};
#endif
类实现文件,嗯。。。我是又爱又恨哪
graph.cpp:
#include "graph.h"
#include"hash_type.h"
#include <iostream>
using namespace std;
Graph::Graph(char ch, int n): symbol(ch), size(n) {}
//draw函数完成初始绘图操作
void Graph::draw()
{
system("CLS");//清屏->输出复位
for(int i=size-1;i>=0;i--)
{
for(int m=0;m<i;m++)
{
cout<<" ";
}
for(int j=0;j<2*(size-i)-1;j++)
{
cout<<symbol;
}
cout<<endl;
}
}
//color函数面对用户设置前景色和背景色的操作
void Graph::color()
{
cout<<"现在您可以设置图形的前景色和背景色"
<<"(请先输入背景色,再输入前景色和背景色)"<<endl
<<"并且请不要使前景色和背景色相同(这样的操作是无效的,将保留上一状态)"<<endl;
while(1)
{
string a,b;
cin>>a;
if(a=="B")///////////////////////////////
{ //
coto='B'; //
break; //
} //
else if(a=="D") //保证能够跳出当前函数
{ //
coto='D'; //
break; //
}////////////////////////////////////////
cin>>b;
recolordraw(a,b);
}
}
//movg函数面对用户移动图形的操作
void Graph::movg()
{
cout<<"现在您可以通过方向键控制图形的移动"<<endl;
while(1)
{
if(getch()==0xE0)//getch()读取输入流时不对输入流进行操作,并且读取一部分以确定是虚拟键值,第二次读取一个整型量确定是哪个键,函数原型在 conio.h 里
{
//system("CLS");//清屏指令
switch(getch())
{
case 72:m[1]--;break;//上
case 80:m[1]++;break;//下
case 75:m[0]--;break;//左
case 77:m[0]++;break;//右
}
removgdraw(m);
}
else
{
cin>>coto;
if(coto=='B')break;//和color函数里的注释部分功能一样
else if(coto=='C')break;
}
}
}
//recolordraw函数负责执行对颜色设置的指令,这个比较坑,两个颜色不能一样,否则不执行。因为这个,我反复调试hash函数,最后还是无意间发现这个问题的
void Graph::recolordraw(string a,string b)
{
s[6]=cdef(a);
s[7]=cdef(b);
system("CLS");//清屏
system(s);
removgdraw(m);
}
//removgdraw函数负责执行对移动图像的操作,本来想用数组存储图像,但这样有较大的局限,所以改用数组存储相对位置,从而输出
//虽然能够对左方边界和上方边界实现越界操作,但是右方和下方不容易实现(按照我的方法必须知道右下方的边界值),除非使用控制台指令。
//控制台命令可以反馈输出是否越界并发出警告信息,也可以反馈某个字符的位置,不过这又要增加变量(用于记录边界字符状态),增加程序的复杂度,所以就没有实现这个功能
void Graph::removgdraw(int* a)
{
system("CLS");//清屏
int k=0;
if(a[1]>0)
for(int i=0;i<a[1];i++)cout<<endl;
else k=a[1];
for(int i=size-1+k;i>=0;i--)
{
int p=0;
for(int m=0;m<i+a[0];m++)
{
cout<<" ";
}
if(i+a[0]<0)p=-a[0]-i;
for(int j=p;j<2*(size-i)-1;j++)
{
cout<<symbol;
}
cout<<endl;
}
}
//cedf函数负责将用户输入字符串转换成用于系统指令的字符,应用了hash值(因为C++中switch_case对象不能为string),除非你想使用16个if_else语句
inline char Graph::cdef(string a)
{
char m;
const char *h=a.data();
switch(hash_s(h))
{
case vphash("black"): m='0';break;
case vphash("blue"): m='1';break;
case vphash("green"): m='2';break;
case vphash("light_blue"): m='3';break;
case vphash("red"): m='4';break;
case vphash("purple"): m='5';break;
case vphash("yellow"): m='6';break;
case vphash("white"): m='7';break;
case vphash("gray"): m='8';break;
case vphash("baby_blue"): m='9';break;
case vphash("pale_green"): m='A';break;
case vphash("light_green"): m='B';break;
case vphash("reddish"): m='C';break;
case vphash("mauve"): m='D';break;
case vphash("faint_yellow"): m='E';break;
case vphash("light_white"): m='F';break;
default: m='0';break;
}
return m;
}
new main.cpp:
hide()函数是从网上找的,主要就是隐藏光标的显示(但是手动改变窗口大小时,会失效)
#include <iostream>
#include "graph.h"
using namespace std;
HANDLE hout=GetStdHandle(STD_OUTPUT_HANDLE);
COORD coord;
//隐藏光标 函数
void hide()
{
CONSOLE_CURSOR_INFO cursor_info={1,0};
SetConsoleCursorInfo(hout, &cursor_info);
}
int main()
{
while(1)
{
char m;
int n;
system("mode con cols=120 lines=30");//设置窗口大小
cout<<"请输入图形的构成字符与尺寸:"<<endl;
hide();//隐藏光标
cin>>m>>n;
Graph graph(m,n);
system("CLS");
graph.draw();
cout<<"你想要对当前图形进行更多操作吗?是(y),否(n)"<<endl;
char ch;
cin>>ch;
system("CLS");
graph.draw();
if(ch=='n')continue;
else if(ch=='y')
{
cout<<"您可以用方向键调整图形的位置(D),并可以设置图形的前景色和背景色(C)"<<endl
<<"您在操作中可以输入C或D以切换至相应的操作"<<endl
<<"当您需要结束对当前图形的操作时,请输入 B "<<endl;
while(1)
{
if(graph.coto!='B'&&graph.coto!='C'&&graph.coto!='D')cin>>graph.coto;
if(graph.coto=='B')break;
else if(graph.coto=='D')graph.movg();
else if(graph.coto=='C')graph.color();
else cout<<"请输入指定的字符,否则我们无法理解您的意图"<<endl;
}
}
graph.coto='0';
system("CLS");
system("color 0F");//颜色复位
}
return 0;
}
new graph.h:
加一张Graph类图,方便看:
#ifndef GRAPH_H
#define GRAPH_H
#include<cstdlib>
#include <conio.h>
#include <windows.h>
#include <string>
#include<vector>
using namespace std;
// 类Graph的声明
class Graph {
private:
char symbol;
int size;
public:
Graph(char ch, int n); // 带有参数的构造函数
void draw(); //绘制图形
void redraw();
void graph_clear();
void recolordraw(string a,string b); //重绘图形
void color(); //设置前景色与背景色
void movg(); //移动图形
void SCPo(const int x, const int y);
void CHPo(int m,int y);
void TEB(); //优化体验
char cdef(string a);
char coto='0';//用于记录用户对图形的主操作指令
char s[9]={'c','o','l','o','r',' ','0','F',' '};//初始化,用于执行颜色设置
vector<vector<int>>direction;//利用vector 容器使能够创建任意大小的图案(当然不能超过缓冲区大小啦)
};
new graph.cpp:
用光标函数代替原先的纯数学式输出和清屏指令,体验更好
实现了上下左右都能越界的操作(其实只是设置窗口初始大小,从而实现越界,如果运行时手动改变窗口大小,就会被看出破绽。。哈哈)
#include "graph.h"
#include"hash_type.h"
#include <iostream>
using namespace std;
//默认构造函数,完成私有变量初始化和点位置初始化
Graph::Graph(char ch, int n): symbol(ch), size(n)
{
int m=size-1;
for(int i=0;i<size;i++)
{
for(int j=i*i,k=m-i;j<(i+1)*(i+1)&&k<m+i+1;j++,k++)
{
direction.push_back(vector<int>());//向 direction 第一层添加一个数组
//向已添加的数组中添加元素
//左上原点坐标(0,0),横纵坐标皆为正值
direction[j].push_back(k);//x,即将输出的点在控制台的横坐标
direction[j].push_back(i);//y,即将输出的点在控制台的纵坐标
}
}
}
//draw函数完成初始绘图操作(隐藏redraw函数)
void Graph::draw()
{
redraw();
}
//re(al)draw函数执行真正绘图的操作
void Graph::redraw()
{
for(int i=0;i<size*size;i++)
{
if(direction[i][0]<0||direction[i][1]<0||direction[i][0]>119||direction[i][1]>28)continue;
//设置了120*30的窗口,但是纵轴只能到第28行,如果设置29行,移动图案一直向下就会看到一个菱形(很奇妙,是不是?)
//设置为30或更高,继续向下首先会出现一堆不规则图案,接着就是很规则的。。。。一条条线
else
{
SCPo(direction[i][0],direction[i][1]);
cout<<symbol;
}
}
cout<<endl;
}
//graph_clear 将之前的图形擦除
void Graph::graph_clear()
{
for(int i=0;i<size*size;i++)
{
if(direction[i][0]<0||direction[i][1]<0||direction[i][0]>119||direction[i][1]>28)continue;
else
{
SCPo(direction[i][0],direction[i][1]);
cout<<" ";
}
}
}
//color函数面对用户设置前景色和背景色的操作
void Graph::color()
{
system("CLS");
redraw();
cout<<"现在您可以设置图形的前景色和背景色"
<<"(请先输入背景色,再输入前景色)"<<endl
<<"并且请不要使前景色和背景色相同(这样的操作是无效的,将保留上一状态)"<<endl;
Sleep(2000);
TEB();
while(1)
{
string a,b;
cin>>a;
if(a=="B")
{
coto='B';
break;
}
else if(a=="D")
{
coto='D';
break;
}
cin>>b;
recolordraw(a,b);
}
}
//movg函数面对用户移动图形的操作
void Graph::movg()
{
system("CLS");
redraw();
cout<<"现在您可以通过方向键控制图形的移动"<<endl;
TEB();
while(1)
{
if(getch()==0xE0)
{
graph_clear();
switch(getch())
{
case 72:CHPo(1,-1);break;//上
case 80:CHPo(1,1);break;//下
case 75:CHPo(0,-1);break;//左
case 77:CHPo(0,1);break;//右
}
redraw();
}
else
{
cin>>coto;
if(coto=='B')break;
else if(coto=='C')break;
}
}
}
//recolordraw函数负责执行对颜色设置的指令
void Graph::recolordraw(string a,string b)
{
s[6]=cdef(a);
s[7]=cdef(b);
system(s);
redraw();
//下面是为了优化体验
TEB();
}
//cedf函数负责将用户输入字符串转换成用于系统指令的字符,增加对大写的支持,同样可以增加对其他名词的支持,不过太多了,就没写
inline char Graph::cdef(string a)
{
char m;
const char *h=a.data();
switch(hash_s(h))
{
case vphash("BLACK"): m='0';
case vphash("black"): m='0';break;
case vphash("BLUE"): m='1';
case vphash("blue"): m='1';break;
case vphash("GREEN"): m='2';
case vphash("green"): m='2';break;
case vphash("LIGHT_BLUE"): m='3';
case vphash("light_blue"): m='3';break;
case vphash("RED"): m='4';
case vphash("red"): m='4';break;
case vphash("PURPLE"): m='5';
case vphash("purple"): m='5';break;
case vphash("YELLOW"): m='6';
case vphash("yellow"): m='6';break;
case vphash("WHITE"): m='7';
case vphash("white"): m='7';break;
case vphash("GRAY"): m='8';
case vphash("gray"): m='8';break;
case vphash("BABY_BLUE"): m='9';
case vphash("baby_blue"): m='9';break;
case vphash("PALE_GREEN"): m='A';
case vphash("pale_green"): m='A';break;
case vphash("LIGHT_GREEN"): m='B';
case vphash("light_green"): m='B';break;
case vphash("REDDISH"): m='C';
case vphash("reddish"): m='C';break;
case vphash("MAUVE"): m='D';
case vphash("mauve"): m='D';break;
case vphash("FAINT_YELLOW"): m='E';
case vphash("faint_yellow"): m='E';break;
case vphash("LIGHT_WHITE"): m='F';
case vphash("light_white"): m='F';break;
default: m='0';break;
}
return m;
}
//S(et)C(onsoleCursor)Po(sition)函数重定位光标位置
void Graph::SCPo(const int x, const int y)
{
COORD position;
position.X = x;
position.Y = y;
SetConsoleCursorPosition(GetStdHandle(STD_OUTPUT_HANDLE), position);
}
//CH(ANGE)Po(sition)函数改变每个点的位置
void Graph::CHPo(int m,int y)
{
for(auto &v:direction)
{
v[m]+=y;
}
}
//TEB函数使程序在运行使停顿 1s ,并清屏重绘图形,使其他无关的东西全部消失
void Graph::TEB()
{
Sleep(1000);
system("CLS");
redraw();
}
将hash值计算函数存放为头文件,不知道为什么,将函数放在graph.h里会提示重复定义,没办法,只能新建一个头文件。可能是位置不对吧
hash函数可以自定义,只要能将数据转换为一个hash值,都可以称作hash函数(如果不需要加密的话),但运行的高效性和hash值冲突(两个不同字符串(也就是两个不同的输入数据)计算出的hash值一样)的良好解决才是一个hash函数的精髓。
通常编译器自带hash函数(在functional.h头文件里),但是编译器没办法在编译期对switch_case对象进行hash值计算,也就是说以上方的形式编写是无法通过编译的,那么如果想使用编译器自带hash函数,只能事先把将要匹配的数据转换为hash值(constexpr 常量表达式 ?),再写入case对象中。
所以说,使用上述方法是非常麻烦的(尤其是匹配对象非常多或者可变换的时候 )。
其实只要知道hash值的计算方法,就可以自建一个constexpr递归函数,用此函数计算case对象的hash值就可以通过编译了,即上面的代码
但通常我们是看不到编译软件中的函数原型的,可能是我不知道怎么看?(我唯一 一次看到头文件是因为cout输出格式出现问题,编译器提示在头文件中有记载并可以点开看)
所以只能自己编写或使用经典的算法,其中有BKDRhash(暴雪公司),APhash,DJBhash等等,有兴趣的话可以看看这篇转载博客(原文网址好像失效了)
hash算法总结收集 - CSDN博客
https://blog.csdn.net/Together_CZ/article/details/72903106
下面这个是从网上找的,选择这个不是因为算法有多好,而是自带了constexpr函数(哈哈。。毕竟,递归计算hash值时有些东西我也不懂啊)
hash_type.h:
#ifndef HASH_TYPE_H_INCLUDED
#define HASH_TYPE_H_INCLUDED
#include <string>
using namespace std;
using hash_type=uint64_t;
constexpr hash_type prime = 0x100000001B3ull;
constexpr hash_type basis = 0xCBF29CE484222325ull;
hash_type hash_s(char const* str)
{
hash_type ret{basis};
while(*str)
{
ret ^= *str;
ret *= prime;
str++;
}
return ret;
}
constexpr hash_type vphash(char const* str, hash_type last_value = basis)
{
return *str ? vphash(str+1, (*str ^ last_value) * prime) : last_value;
}
#endif // HASH_TYPE_H_INCLUDED
下面让我们来看看效果(不会录制屏幕,所以没办法制成GIF动图):
初始界面:
输入* 12后并选择继续操作后的界面:
继续,输入 D :
使用方向键移动至顶端并越界:
使用方向键移动至左端并越界:
想换个颜色时输入 C :
换好了!(图中文字仅为注释而添加):
换个颜色继续动:
想结束时输入 B 将回到初始界面。
Fraction类:
分数类就没什么知识点了,主要就是运算符重载的问题
Fraction类图:
main.cpp:
#include <iostream>
#include"Fraction.h"
using namespace std;
int main()
{
Fraction a(5,-2);
Fraction b(-1,-2);
Fraction f(1,2);
Fraction c;
Fraction g;
g=a*b;
show(g);
g=a/b;
show(g);
c=a-b;
compare(a,b);
compare(b,f);
show(c);
trshow(c);
double m;
m=tranf_d(c);
cout<<m<<endl;
m=tranf_d(a);
cout<<m<<endl;
m=tranf_d(b);
cout<<m<<endl;
m=tranf_d(f);
cout<<m<<endl;
m=tranf_d(g);
cout<<m<<endl;
Fraction p(1635,-135);
trshow(p);
return 0;
}
Fraction.h:
#ifndef FRACTION_H_INCLUDED
#define FRACTION_H_INCLUDED
class Fraction
{
private:
int top;
int bottom;
public:
Fraction();
Fraction(int t0,int b0);
Fraction(int t0);
Fraction(const Fraction &t);
~Fraction();
Fraction operator+(const Fraction &a);//使用运算符重载时必须使用'operator'关键字,否则会报错(亲测有效)
Fraction operator-(const Fraction &a);
Fraction operator*(const Fraction &a);
Fraction operator/(const Fraction &a);
friend void show(Fraction a);
friend double tranf_d(Fraction a);
friend void compare(Fraction a,Fraction b);
friend int codx(int x,int y);
};
#endif // FRACTION_H_INCLUDED
Fraction.cpp:
#include<iostream>
#include<cmath>
#include<cstring>
#include"Fraction.h"
using namespace std;
inline int codx(int x,int y)//求最大公约数
{
int t=0;
int a,b;
a=x;
b=y;
while(b!=0)
{
t=a%b;
a=b;
b=t;
}
return a;
}
Fraction::Fraction():top(0),bottom(1){}
Fraction::Fraction(int t0,int b0):top(t0),bottom(b0)
{
//if(bottom==0)
//throw runtime_error("Warning!The bottom can't be 0.");//嗯。。。这个最好现在还是不要加,没啥意义
if(bottom<0||(top<0&&bottom<0))
{
top=0-t0;
bottom=0-b0;
}
}
Fraction::Fraction(int t0):top(t0),bottom(1){}
Fraction::Fraction(const Fraction &t):top(t.top),bottom(t.bottom){}
Fraction::~Fraction(){}
//下面是实现分数类四则运算的具体代码
Fraction Fraction::operator+(const Fraction& a)
{
Fraction c;
int p,q,m;
p=bottom*a.bottom/codx(bottom,a.bottom);
q=p/bottom*top+p/a.bottom*a.top;
m=codx(p,q);//m是必要的,因为1/2+5/2应该存储显示为3/1而不是6/2
c.bottom=p/m;
c.top=q/m;
return c;
}
Fraction Fraction::operator-(const Fraction& a)
{
Fraction c;
int p,q,m;
p=bottom*a.bottom/codx(bottom,a.bottom);
q=p/bottom*top-p/a.bottom*a.top;
m=codx(p,q);
c.bottom=p/m;
c.top=q/m;
return c;
}
Fraction Fraction::operator*(const Fraction& a)
{
Fraction c;
int m,n,q;
m=bottom*a.bottom;
n=top*a.top;
q=codx(m,fabs(n));
c.top=n/q;
c.bottom=m/q;
return c;
}
Fraction Fraction::operator/(const Fraction& a)
{
Fraction c;
int m,n,q;
m=bottom*a.top;
n=top*a.bottom;
q=codx(fabs(m),fabs(n));
c.top=n/q;
c.bottom=m/q;
return c;
}
//这些都是非成员函数,通过friend成为友元使其能够访问类的私有变量
//其实这些函数都可以被定义为成员函数,但是成为成员函数后它们的使用方式对用户不太友好,比如 compare(a,b); 写成 a.compare(b); 显然和我们的习惯不符
//这也是编程语言面向对象的意义所在(数据保护是另一方面)
void show(Fraction a)//输出很揪心,因为C++没有反射,所以无法以实例名输出(如果有人知道怎么操作的话,请在评论中说明,谢谢)
{
cout<<a.top<<'/'<<a.bottom<<endl;
}
//以传统方式输出分数
void trshow(Fraction a)
{
//计算分子分母的位数以确定输出线的长度
int topd=digit(fabs(a.top));
int botd=digit(fabs(a.bottom));
int m=max(topd,botd);
if(a.top<0)
{
cout<<'-';
couof((m-topd)/2," ");
}
else
couof((m-topd)/2+1," ");
cout<<fabs(a.top)<<endl;
couof(m+1,"━");
cout<<endl;
couof((m-botd)/2+1," ");
cout<<a.bottom<<endl;
}
double tranf_d(Fraction a)
{
double m;
m=a.top/a.bottom;//感谢老师指出这个问题,a.top和a.bottom都是整型,必须乘以一个1.0才能转换为double型
return m;
}
void compare(Fraction a,Fraction b)
{
if(a.top*b.bottom==a.bottom*b.top)
cout<<a.top<<'/'<<a.bottom<<'='<<b.top<<'/'<<b.bottom<<endl;
else
cout<<a.top<<'/'<<a.bottom<<
(a.top*b.bottom>a.bottom*b.top?'>':'<')
<<b.top<<'/'<<b.bottom<<endl;
}