实验四——再探类

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;
}

原文地址:https://www.cnblogs.com/BuluGuy/p/8890472.html