C/C++语言实现图形化排序计算器

前言

        在C语言的学习中,一直面对黑色命令框一定十分令人乏味。本次我将带领大家使用easyx实现图形化排序计算器,我使用的版本是VS2022+EasyX_2023大暑版。不同版本可能细节上处理不同,大家记得修改,否则不能成功运行。

QQ录屏20240317145000

功能

        首先我们要明白我们这次设计的计算器要实现什么功能,要做什么?顾名思义,我们想要做的也十分的简单,就是输入一堆有效数字,然后让程序给我们计算出来正确的顺序并且输出出来。

        具体实现完如下

绘制计算器轮廓

        既然是叫做图形化界面,那么必然有相应的图像才可以。我们依据以往的计算器模样大致的可以猜想我们要实现计算器的模样,在这里我推荐大家在纸上或者是电脑上画出大致的轮廓。如下图。

创建窗口

        首先我们将创建一个窗口用于显示计算器,在官网EasyX Graphics Library for C++下载好后,只需要包含头文件#include<graphics.h>就可以使用Eaxy函数了。在Eaxy中创建一个窗口十分简单,只需要使用如下函数

HWND initgraph(
	int width,
	int height,
	int flag = NULL
);

这里的 width指绘图窗口的宽度。heigh纸t绘图窗口的高度。注意这与我们日常不同的是他的原点在左上角即他的坐标轴是如下的.这点要尤其注意

        经过调试创建了500*600的窗口大小,直接使用initgraph(500, 600);就可以了。

画直线

        如果我们仔细地观察不难发现,我们1,2,3,……按钮与上方显示器区分的线条都是直线,那么我们便可以通过画直线的方式确定我们按钮的位置。

        在Easyx中画直线要用到如下函数

void line(
	int x1,
	int y1,
	int x2,
	int y2
);

        这个也十分容易理解,在一个平面中,两点确定一条直线,给出两点的坐标显然可以画出这个直线。

        接下来重要i的是划分每条线在哪里。我们可以提前规划好每个按钮的大下。根据我们最初设想的样子,可以画出如下图规划。

        有了这张坐标图当我们在使用一些函数绘图时便可以了然于心。画直线的代码也十分简单了,如下。

//画下方按钮
line(0, 300, 500, 300);
line(0, 375, 500, 375);
line(0, 450, 500, 450);
line(0, 525, 500, 525);

line(125, 300, 125, 600);
line(250, 300, 250, 600);
line(375, 300, 375, 600);
//画正上方输出屏
line(170, 0, 170, 300);
line(340, 0, 340, 300);

 画按钮

        光有空白十分不方便我们以后使用,如果在具体的位置有文字便更好了。于是接下来我们便输出按钮内的文字。

drawtext

        首先我们要了解一个函数drawtext。它可以在窗口内输出文字。但不能用printf,他是在黑色命令框内输出文字。

int drawtext(
	LPCTSTR str,
	RECT* pRect,
	UINT uFormat
);

    首先看第一个参数,str,他的类型其实就是双字节指针,与我们日常用的char 不同,我们不加处理“”用双引号括出来的是单字节字符串,不能直接传参,这也是2023版的改动。大家要注意不同版本的细节。我们可以将VS配置调整或采用以下方式转换。将单字节转换为双字节。但也要注意的是双字节的编码也兼容ASCII表,相当于在ASCII表示扩容。后续会用到这一特点。

L"确认"
_T("确认")

        第二个参数显然是一个指针,但其指向的是什么呢?如下例,它RECT其实就是一个矩形的位置坐标,在平面中只要确定两个相对顶点的坐标,矩形便确定了。这个类型其实就是矩形左上与右下顶点的坐标,我们输出文字其实就是在一个个矩形内输出。

RECT r = {0, 0, 639, 479};

        他的第三个参数是矩形内文字的处理,左对齐,右对齐,独行等,只截取如下供读者查看,用到的时候在上官网查看即可。

结构体

        我们输出文字后,后面还会用到按钮的内容,位置信息么?显然是会的,我们在最后还要通过鼠标输入数据,点击一下鼠标,只有检测鼠标在那个按钮内我们才可以确定输入的内容。我们每次检验,输出都可以通过手写RECT r = {0, 0, 639, 479};

drawtext(_T("Hello World"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);来实现,但这太过麻烦了!!于是我们便可以通过定义一个结构体来存储一次位置,内容信息,以后直接通过结构体访问,减小记忆数据的负担。我们定义如下结构体

//按钮
struct button
{
	int x_left;
	int y_left;
	int x_right;
	int y_right;
	COLORREF color;
	WCHAR str[100];
}; 

        细心的读者一定发现按钮的结构体多了个COLORREF color;颜色成员,这个我们在下文再具体讲。 str[100]是为了考虑后面可能的需要。设置成10也可以。

初始化按钮

位置

        有了结构体后我们可以开始定义结构体了,然后对其进行初始化。根据我们的规划,我们下半窗口一共有16个按钮,相信大家第一反应便是如下创建结构体

struct button b0;
struct button b1;
struct button b2;
struct button b3;
struct button b4;
struct button b5;
struct button b6;
struct button b7;………………

        一共有16个,创建16次便可以了,然后依次初始化。这样固然可以但其实我们还有更好的方法便是创建一个结构体数组。我们在仔细观察下表。我们要初始化的内容无非就是坐标信息和文字,文字我们肯定要一个个的初始化。但坐标可以简单些么?看这下面的图是不是特别像二维数组,再联想一些。在第一行的遍历中每一行的x是每次加125,y不变,在第二行x还是每次加125,y相较于最初的0,300,加了75.这仿佛是某种规律!!如果我们把行设为i1,列设为i2,每一个按钮左顶点x就是i2 * 125,y就是300 + 75 * i1;!!!这样我们就可以写出如下代码简化初始化的过程。

//初始化
count = 0;
for (i1 = 0; i1 < 4; i1++)
{
	for (i2 = 0; i2 < 4; i2++)
	{
		arr[count].x_left = i2 * 125;
		arr[count].x_right = 125+i2*125;
		arr[count].y_left = 300 + 75 * i1;
		arr[count].y_right = 375 + 75 * i1;
		count++;
	}
}

        这样做大大简化了代码,简直就是太库拉。

文字

        接下来就是初始化文字内容了。 面对数字我们可以使用如下方法。

for (i = 0; i < 3; i++)
{
	arr[i].str[0] = i + L'1';
	arr[i].str[1] = '\0';
}
for (i = 0; i < 3; i++)
{
	arr[i+4].str[0] = i + L'4';
	arr[i+4].str[1] = '\0';
}
for (i = 0; i < 3; i++)
{
	arr[i + 8].str[0] = i + L'7';
	arr[i + 8].str[1] = '\0';
}

        在这里要注意的是数字并不是紧挨着的,我们采用了数组的方式存储,那么按钮的位置应该如下图分布。

        而对于文字我们可以我们不可以简单的用以下方式,arr[13].str = L"开始",应为此时的str是一个数组的地址,他是不可修改的左值,这种初始化只有在定义的时候才可以用。我们便有如下的两种方式

第一种

        我们知道一个汉字就是一个双字节大小,便可以如下初始化。

arr[14].str[0] = L'删';
arr[14].str[1] = L'除';
arr[14].str[2] = L'\0';

        经管他是一个双字节字符串他任然以\0作为字符串结束标志。‘\0‘千万不能忘记加,否则会产生难以预料的错误。

第二种

         我们可以单独写一个Copy函数来解决它,注意不能用strcpy,因为strcpy的参数是单字节!!但两者实现的思路一模一样。

//复制字符串
void Copy(WCHAR* des, const WCHAR* src)
{
	while (*des++ = *src++);
}

        注意while中=是赋值而不是判断,这也是精妙之处!Copy(arr[14].str, L"删除");假设我们调用了这个函数,src就指向了"删除",des在数值上等于str,str是数组名不可改变,但此时des是变量可以改变,将src解引用后的内容不断赋值给des,这个赋值表达式的结果就是*des,当没遇到\0是,这个循环就成立接着进入循环,当*des为\0是赋值停止。

//按钮文字
Copy(arr[3].str, L"确认");
Copy(arr[7].str, L"升序");
Copy(arr[11].str, L"降序");
Copy(arr[15].str, L"开始");
Copy(arr[14].str, L"删除");

        按照上述的方法便可以全部初始化完按钮了。

输出文字

        经过上面的初始化后我们便可以输出对应的文字了。settextstyle是设置文字的大小,字体。

//输出按钮文字
void DrawButtonDown(ST *pa)
{
	RECT r = {0,0,0,0 };
	int i = 0;
	for (i = 0; i < 16; i++)
	{
		settextstyle(40, 0, L"楷体");
		RECT r = { arr[i].x_left,arr[i].y_left,arr[i].x_right,arr[i].y_right };
		fillrectangle(arr[i].x_left, arr[i].y_left, arr[i].x_right, arr[i].y_right);
		drawtext(arr[i].str, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	}
	
}

        我们便可以得到下图

思考

    写到这如果我们一直都在main函数中写那么,大概有小100行了,接下来一定还有很多的代码,如果我们都在main中写固然可以,但程序的主逻辑必然难以观察,那个功能写错了进行维护也十分困难,对应的代码可能混乱无序。于是我们便可以采用如下方法

模块化编程

   我们可以将一些实现特点功能的代码封装在函数中,然后再main函数中调用即可,简化主函数中的代码,尽量的体现逻辑性。

 结构体维护

        然后我们还要注意的是,我们必定要定义一些变量存储信息,在函数调用中传递相应的参数,在这里我推荐把必须的变量放在一起,封装成结构体,在传参数就可以传一个结构体指针来调用想要的变量,而不是一个一个传所需要的参数。

        后面排序必定会有升序和降序两种,我们可以用枚举的方式增加代码可读性。

其次接下来我们为了可以持续的维持程序运行,知道按下开始,可以采用一个while死循环的方式,当检测到鼠标点击后结束程序。我们可以换种方法维护程序的运行。增加个运行状态成员,同样采用枚举的方式如下。

//排序方式
enum sort
{
	UP,
	DOWN
};

//运行状态
enum status
{
	OK,
	START
};


//整个界面对象
typedef struct syetem
{
	struct button arr[16];
	enum status status;
	enum sort sort;
}ST;

用如下while方式保持程序的运行

while (a.status == OK)
{
	……
    ………
    …

}

鼠标提示

        如果我们的鼠标在按钮的上方,按钮就会出现变化,那么就可以得到很好的反馈。接下来我们便开始实现这一功能。

        在此我们可以用一个函数实现这一功能,之前绘画按钮也可以封装成一个函数,变量通过传入的指针使用。

int main()
{
	ST a;//以结构体维护系统


	//初始化
	init(&a);
	
	while (a.status == OK)
	{
		//跟踪鼠标
		CheckInButton(&a);

	}

	return 0;
}

鼠标信息

        ExMessage变量用于存储获取信息,他的内容有以下。可以表示鼠标和键盘的信息。

struct ExMessage
{
	USHORT message;					// 消息标识
	union
	{
		// 鼠标消息的数据
		struct
		{
			bool ctrl		:1;		// Ctrl 键是否按下
			bool shift		:1;		// Shift 键是否按下
			bool lbutton	:1;		// 鼠标左键是否按下
			bool mbutton	:1;		// 鼠标中键是否按下
			bool rbutton	:1;		// 鼠标右键
			short x;				// 鼠标的 x 坐标
			short y;				// 鼠标的 y 坐标
			short wheel;			// 鼠标滚轮滚动值,为 120 的倍数
		};

		// 按键消息的数据
		struct
		{
			BYTE vkcode;			// 按键的虚拟键码
			BYTE scancode;			// 按键的扫描码(依赖于 OEM)
			bool extended	:1;		// 按键是否是扩展键
			bool prevdown	:1;		// 按键的前一个状态是否按下
		};

		// 字符消息的数据
		TCHAR ch;

		// 窗口消息的数据
		struct
		{
			WPARAM wParam;
			LPARAM lParam;
		};
	};
};

        getmessage用于获取当前的输入信息。其中的参数可以不写,这是C++重载的方式。

ExMessage getmessage(BYTE filter = -1);

        参数不同,获取的信息不同。

遍历比较

        鼠标在按钮内如何判断呢?只需要检测鼠标的坐标是不是在按钮的矩形框内就可以了。

因为我们采用了数组的方式存储按钮结构体,一个for循环便可以遍历了。此时还记得我们之前按钮结构体加了个颜色成员么?如果检测到鼠标在按钮内就改变其结构体内的颜色。主义此时输出还没有改变。

        在之前绘画按钮的程序可以在封装成一个函数,在最后绘画按钮即可体现变化。

相关知识

setfillcolor是设置填充颜色。我们可以由此画出灰色显示鼠标的位置。

fillrectangle是矩形填充,可以将原来的白色覆盖

void fillrectangle(
	int left,
	int top,
	int right,
	int bottom
);

       RGB可以用于合成某一种颜色

COLORREF RGB(
	BYTE byRed,		// 颜色的红色部分
	BYTE byGreen,	// 颜色的绿色部分
	BYTE byBlue		// 颜色的蓝色部分
);

        每一种颜色都可以通过红绿蓝合成。当然Easyx也提供了一些简单颜色。REN BLACK等

        由此我们便可以写出如下封装好的函数

//输出按钮文字
void DrawButtonDown(ST *pa)
{
	RECT r = {0,0,0,0 };
	int i = 0;
	for (i = 0; i < 16; i++)
	{
		settextstyle(40, 0, L"楷体");
		RECT r = { pa->arr[i].x_left,pa->arr[i].y_left,pa->arr[i].x_right,pa->arr[i].y_right };
		setfillcolor(pa->arr[i].color);
		fillrectangle(pa->arr[i].x_left, pa->arr[i].y_left, pa->arr[i].x_right, pa->arr[i].y_right);
		drawtext(pa->arr[i].str, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	}
	
}

        经过上述准备便可以写出如下的代码。

//检测鼠标在按钮内
void CheckInButton(ST * pa )
{
	//获取鼠标信息
	flushmessage();
	pa->m = getmessage();

	//检测在下方的按钮,在则表示为灰色
	ExMessage m = pa->m;
	int i1 = 0, i2 = 0;
	int count = 0;
	for (i1 = 0; i1 < 4; i1++)
	{
		for (i2 = 0; i2 < 4; i2++)
		{
			if (pa->arr[count].x_left < m.x && m.x < pa->arr[count].x_right
				&& pa->arr[count].y_left<m.y && pa->arr[count].y_right>m.y)
			{
				pa->arr[count].color = RGB(127,127,127);
			}
			else
			{
				pa->arr[count].color = WHITE;
			}
			count++;
		}
	}
	DrawButtonDown(pa);

	//刷新缓冲区
	FlushBatchDraw();
}

        注意看最后有个FlushBatchDraw();可以理解为我们画的图不会直接画在窗口上,而是先储存在缓存区,当缓存区满的时候在输出到窗口,而 FlushBatchDraw();可以刷新缓冲区,让目前缓冲区的内容输出到窗口上。这个有利于我们观察代码,否则可能代码执行完,窗口却毫无变化。

        可以实现鼠标在哪,那个按钮颜色变化。(截图无法截取到鼠标)

处理点击信息

        这一块显然是整个程序的核心。数据的输入,输出,排序的方式都依靠这个实现,同样我们可以将它封装成一个函数。这样写我们程序的整体思想便清晰可见。

int main()
{
	ST a;//以结构体维护系统

	//初始化
	init(&a);
	
	while (a.status == OK)
	{
		//跟踪鼠标
		CheckInButton(&a);
		
		//获取点击信息
		Get(&a);

	}

	return 0;
}

        接下来便是具体实现的过程了。

        首先我们要检测的便是鼠标左键有没有按下,如果按下pa->m.lbutton 就为1,便执行下面的程序。不为1就没点击不需要处理。于是我们便可以采用如下的框架。

//获取鼠标信息
void Get(ST *pa)
{
	int i = 0;
	//检测鼠标按下
	if (pa->m.lbutton == 1)
	{
		//检测鼠标位置
		for (i = 0; i < 16; i++)
		{
			if (pa->arr[i].color != WHITE)
				break;
		}

		if (i!=3 && i!=7 && i!=11 && i!=15 && i!=14)//输入数字
		{
			
		}
		else if(i==3)//确认
		{
			
		}
		else if (i == 14)//删除
		{
		
		}
		else if (i == 11)//降序
		{
			
		}
		else if (i == 7)//升序
		{
			
		}
		else if (i == 15)//开始
		{
			
		}
	}
	
	FlushBatchDraw();
}

        接下来只需要按功能填写就可以了。

        首先我们点击一个一次1我们获取的信息最终要输出在窗口上,drawtext不像printf一样可以格式化输出,只能输出字符串,也就是我们输出的数字要被视为是字符串。所以我们要以双字节的形式存储我们的点击信息,便于后续的输出。

        此时我们便可以在struct syetem中添加成员来记录输入数据。

        我们最终要输入多个双字节字符串然后排序,可以使用二维数组的方式,为了方便清除我们已经输入到第几个数组,可以加个size来记录。其次我们对于字符串不好排序,可以用一个浮点数组储存数字,便于比较。对于这几个紧密关联的成员,我们可以在将他们封装成一个结构体,最终有如下代码。(WCHAR是宽字符,也就是双字节)

        这基本包含了我们代码前的所有准备。

//按钮
struct button
{
	int x_left;
	int y_left;
	int x_right;
	int y_right;
	COLORREF color;
	WCHAR str[100];
}; 



//输出控制
struct out
{
	WCHAR str[20][30] = { 0 };
	int size;
	double strnum[20];
};

//运行状态
enum status
{
	OK,
	START
};

//排序方式
enum sort
{
	UP,
	DOWN
};

//整个界面对象
typedef struct syetem
{
	struct out out;
	struct button arr[16];
	ExMessage m;
	enum status status;
	enum sort sort;
}ST;

        在VS中结构体中的双字节数组自动全部初始化为\0,在这里就不需要在手动初始化了。

点击数字

        

        因为我们设置了鼠标在按钮上方,按钮的颜色改变,所以我们可以通过颜色判断鼠标在那个按钮。如果i!=3 && i!=7 && i!=11 && i!=15 && i!=14说明鼠标点击的是数字的组成部分我们应当将其存储在字符数组中。此时我们要把这个字符存在哪里呢?显然他是在数组

    pa->out.str[pa->out.size]中但在那个位置呢?是在有效字符后的第一个\0,此时我们就可以先写一个函数,求数组有效字符数,注意不能用strlen,双字节与单字节类型不匹配。

//求字符串长度
int Len(WCHAR* str)
{
	int sz = 0;
	while (*str++)
	{
		sz++;
	}
	return sz;
}

        然后有了上述函数我们便可以知道字符添加的位置了。

if (i!=3 && i!=7 && i!=11 && i!=15 && i!=14)//输入数字
{
	int sz = Len(pa->out.str[pa->out.size]);
	pa->out.str[pa->out.size][sz++] = pa->arr[i].str[0];
	pa->out.str[pa->out.size][sz] = '\0';
}

     arr是按钮结构体数组。

确认

        当i==3的时候就是确认了,他表示当前的一个数字已经输入完毕,可以开始下一次输入了。

在这里先介绍sscanf函数,便于我们后续的操作。

sscanf

int sscanf ( const char * s, const char * format, ...);

        看名字就知道他与scanf有十分紧密的关系,当我们在黑色命令框输入1253时我们输入的其实对于计算器而言是字符串,我们的格式控制符决定了计算器如何看待这些字符串。同样sscanf它可以从一个字符串中格式化读取数据。于scanf从命令框得到字符串不同,sscanf是用户自己提供字符串。于是便可写出如下的代码。

sscanf("12.3", "%lf", &a);

        读取的数据就会存储在a中,与scanf用法基本相同。

        当点击确认时,我们要将输入的字符串转为浮点数存储,便可以用到sscanf,当然也可以自己手写一个函数,但不推荐。其次我们要注意的是sscanf类型为单字节,而我们存储的都是双字节,在使用前要先进性转换。

Swap

        在此我们可以设计一个函数,让其从双字节转换为单字节。

        下面这个代码仿佛什么都没做,但却是将双字节转换为单字节。还记得我们之前讲的么,双字节的Unicode实在ASCII表上扩充的,我们输入的数字点号都在ASCII表中。1在ASCII表中的值为49,在Unicode编码表中也是49,这是为了兼容ASCII表。由此下面的程序才是正确的。

//将宽字符转为窄字符
void swap(char *des, WCHAR* str)
{
	while (*des++ = (char)*str++);
}

        综上我们便可以写出如下的确认代码。注意此时的size要加一,表示输入下一个数据。

else if(i==3)//确认
{
	//字符转数字,便于比较
	char tmp[100] = { 0 };
	swap(tmp, pa->out.str[pa->out.size]);
	sscanf(tmp, "%lf", &pa->out.strnum[pa->out.size]);
	pa->out.size++;
}

删除

        删除十分简单只要将当前的接受数组第一个字符设置为\0即可。然后将当前位置填空。

else if (i == 14)//删除
{
	pa->out.str[pa->out.size][0] = '\0';
	setfillcolor(WHITE);
	solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
}

        这里的solidrectangle与之前的fillrectangle十分像,区别是前者是无边框填充,后者是有边框填充填充。两者用哪一个都可以。

void solidrectangle(
	int left,
	int top,
	int right,
	int bottom
);

降序,升序

        这里的降序只是个标志,决定我们后面如何排序,所以代码也十分简单。

else if (i == 11)//降序
{
	pa->sort = DOWN;
}
else if (i == 7)//升序
{
	pa->sort=UP;
}

开始

        这个标志着我们程序的结束,我们按要求输出最后排序的结果就可以了。在这里我们可以封装一个函数用来处理,排序等操作。结构如下

else if (i == 15)//开始
{
	pa->status = START;
	Start(pa);
}

        在这里我们只需要改变结构体内的状态,当再一次a.status == OK判断时,循环就会结束。

接下来我们开始Start的实现。

      Start

        首先我们便要根据升序还是降序来对数组进行排序,还记得我们之前sscanf(tmp, "%lf", &pa->out.strnum[pa->out.size]);么,我们把输入的字符数组转换为浮点数多保存一份,这无疑方便我们接下来的排序。

        首先我们认识一个函数qsort,它可以按照我们的意思排序任何类型的数据。base是要排序数组地址,num是要排序的元素个数,size是元素大小,cmoper是自定义比较函数。

void qsort (void* base, size_t num, size_t size,
            int (*compar)(const void*,const void*));

        我们着重看下cmoper,我们可以把他的返回值分为三类,等于0,不交换,大于0,p1在p2之后。小于0,p1在p2之前。具体的运用要具体分析。

        我们便可以写出如下排序的框架。

//排序
if (pa->sort == DOWN)
{
	qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpdown);
}
else
{
	qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpup);
}

        接下来就是实现cmpdown函数,由于这是降序,最大的一定在第一个,注意这里的参数必须按要求设置为const void *不能直接解引用,要先强制转换为某种类型。

        *(double*)p2 > *(double*)p1如果进行上述比较,假设p2大,按照我们降序的排布,p1的元素应该在p2后面,所以返回1正数,如果*(double*)p2 > *(double*)p1不为真返回-1。

//排序
int cmpdown(const void* p1, const void* p2)
{

	if (*(double*)p2 > *(double*)p1)
	{
		return 1;
	}
	else
	{
		return -1;
	}

}

        这里的第二个返回值不要写成0,尽管在某些时候负数与0的效果一样。假设就该返回0,返回-1交换一下也无伤大雅,但一但你通通返回0,原来要交换的地方可能就由于返回0就不交换了。

        同理升序的比较函数也可以如下写。

int cmpup(const void* p1, const void* p2)
{

	if (*(double*)p1 > *(double*)p2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
}
   输出调整

        当我们比较完大小后便要再将数字转换为字符,方便我们drawtext输出。

//数字转字符
void chtonum(WCHAR* str,  double m, ST* pa,int i)
{
	char arr[100] = { 0 };
	int flag = 0;

	//检测有无小数
	if (pa->out.strnum[i] - (int)pa->out.strnum[i] > 0.000001 || pa->out.strnum[i] - (int)pa->out.strnum[i] < -0.000001)
	{
		flag = 1;
	}
	flag==0?sprintf(arr, "%.0lf", m): sprintf(arr, "%.4lf", m);
	char* p = arr;
	while (*str++ = (WCHAR)*p++);
}

        上面的if是用来判断是小数还是整数,小数则读取4位小数,sprintf与printf十分像,不过前者是输出在字符串内。最后我们在用个while循环复制。

        最终代码如下

//开始排序
void Start(ST*pa)
{
	//排序
	if (pa->sort == DOWN)
	{
		qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpdown);
	}
	else
	{
		qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpup);
	}
	int i = 0;
	//数字转字符输出
	for (i = 0; i < pa->out.size; i++)
	{
		chtonum(pa->out.str[i], pa->out.strnum[i], pa, i);
	}
	DrawNumUp(pa);
	FlushBatchDraw();
}

        到这里我们的点击函数基本完成了,具体如下。

//获取鼠标信息
void Get(ST *pa)
{
	int i = 0;
	//检测鼠标按下
	if (pa->m.lbutton == 1)
	{
		//检测鼠标位置
		for (i = 0; i < 16; i++)
		{
			if (pa->arr[i].color != WHITE)
				break;
		}

		if (i!=3 && i!=7 && i!=11 && i!=15 && i!=14)//输入数字
		{
			int sz = Len(pa->out.str[pa->out.size]);
			pa->out.str[pa->out.size][sz++] = pa->arr[i].str[0];
			pa->out.str[pa->out.size][sz] = '\0';
		}
		else if(i==3)//确认
		{
			//字符转数字,便于比较
			char tmp[100] = { 0 };
			swap(tmp, pa->out.str[pa->out.size]);
			sscanf(tmp, "%lf", &pa->out.strnum[pa->out.size]);
			pa->out.size++;
		}
		else if (i == 14)//删除
		{
			pa->out.str[pa->out.size][0] = '\0';
			setfillcolor(WHITE);
			solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		}
		else if (i == 11)//降序
		{
			pa->sort = DOWN;
		}
		else if (i == 7)//升序
		{
			pa->sort=UP;
		}
		else if (i == 15)//开始
		{
			pa->status = START;
			Start(pa);
		}
		
	}
	
	
	FlushBatchDraw();
}

    屏幕上方的数字

        到这里我们就要考虑输出最后要排序的数字了。同样我们可以类比画下方按钮的方法,将屏幕上方划分为15个按钮,每个里面有对应的数字字符串。

        此时加入这个新变量的方法也十分的简单,只需要在结构体中加一个成员就可以了,之前写的函数参数也不用做任何改变,只需要通过结构体指针就可以了。

//整个界面对象
typedef struct syetem
{
	struct out out;
	struct button arr[16];
	ExMessage m;
	enum status status;
	enum sort sort;
	struct button num[15];
}ST;

        至此,我们全部的结构体成员以及完成了。我们可以在init(&a);中初始化num数组,init的参数无需改变,在我们之前写的初始化按钮下方写即可。

int i1 = 0, i2 = 0, i = 0;
int count = 0;
//初始化数字按钮
for (i1 = 0; i1 < 3; i1++)
{
	for (i2 = 0; i2 < 5; i2++)
	{
		pa->num[count].x_left = i1 * 170;
		pa->num[count].x_right = 170 + i1 * 170;
		pa->num[count].y_left = i2 * 60;
		pa->num[count].y_right = 60 + i2 * 60;
		pa->num[count].color = WHITE;
		count++;
	}
}

        此时便可以模仿上面的按钮输出,写出下方的输出数字。这里要注意的是加了个动态设计字体大小,主要是预防数字太多,屏幕装不下就减小字体。具体的动态参数要根据情况调整。

//绘画数字
void DrawNumUp(ST* pa)
{
	int i = 0;
	for (i = 0; i < pa->out.size; i++)
	{
		int sz = Len(pa->out.str[i]);
		if (sz > 5)
		{
			settextstyle(170 / sz * 1.8, 0, L"楷体");
		}
		else
		{
			settextstyle(40, 0, L"楷体");
		}

		//设置坐标填充写字
		RECT r = { pa->num[i].x_left,pa->num[i].y_left,pa->num[i].x_right,pa->num[i].y_right };
		setfillcolor(pa->num[i].color);
		fillrectangle(pa->num[i].x_left, pa->num[i].y_left, pa->num[i].x_right, pa->num[i].y_right);
		drawtext(pa->out.str[i], &r, DT_LEFT||DT_SINGLELINE);
	}

	FlushBatchDraw();
}

        到了这里我们基本完成了整个程序的设计但,还有些问题。

        

优化一

       点击数字时,让屏幕有反应,不等到点击确认屏幕才出现数字。这原因也十分简单,没有点击确认前pa->out.size没有加一,当前数字不一定是最终的数字。

        在Get函数中封装一个函数用来实时显示数字。DrawNumUp最多显示到pa->out.size-1的数字,我们只需要让这个函数显示pa->out.size的数字就可以了。

        在这里我们还添加了一个下划线表示当前数字是正在输入的数字。

//输出最后一个元素
void DrawLast(ST* pa)
{
	int sz = Len(pa->out.str[pa->out.size]);
	if (sz != 0)
	{
		if (sz > 5)
		{
			settextstyle(170 / sz * 1.8, 0, L"楷体");
		}
		else
		{
			settextstyle(40, 0, L"楷体");
		}

		RECT r = { pa->num[pa->out.size].x_left,pa->num[pa->out.size].y_left,pa->num[pa->out.size].x_right,pa->num[pa->out.size].y_right };
		setfillcolor(pa->num[pa->out.size].color);
		fillrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		drawtext(pa->out.str[pa->out.size], &r, DT_LEFT || DT_SINGLELINE);
	}
	line(pa->num[pa->out.size].x_left + 60, pa->num[pa->out.size].y_left + 55, pa->num[pa->out.size].x_left + 100, pa->num[pa->out.size].y_left + 55);
}

优化二

        我们可能出现以下的情况,明明鼠标点击一次6,为什么会出现多次结果输入多个6??

        其实原因是我们电脑的性能都十分优秀,跑完一遍代码可能只需要十几毫秒,但我们鼠标从点击到弹起可能需要60ms,这导致了我们点击一次而检测成多次。

        

        为了杜绝这一现象,我们可以先暂停60ms,再用个循环判断解决。

if (pa->m.lbutton == 1)
{

    ……………………
    ……………………
    //消除连续点击鼠标
    Sleep(60);
    while (pa->m.lbutton == 1)
    {
	    flushmessage();
	    pa->m= getmessage();
    }
}

优化三

        单纯的依靠窗口上的删除键,我们只可以删除正在输入的数字,可能我们之前输入的数字有错误,我们便可以提供一种鼠标右键的删除功能,帮助我们快速删除指定数字。

        同样我们可以在Get中封装一个DelMouse(pa);函数来执行这个操作。

        还记得我们之前写的鼠标提示么!数字按钮也是按钮,我们就可以在CheckInButton(&a);函数扩充检测数字现实的功能。具体代码如下。

//检测上方的数字
count = 0;
for (i1 = 0; i1 < 3; i1++)
{
	for (i2 = 0; i2 < 5; i2++)
	{
		if (pa->num[count].x_left < m.x && m.x < pa->num[count].x_right
			&& pa->num[count].y_left<m.y && pa->num[count].y_right>m.y)
		{
			pa->num[count].color = RGB(127, 127, 127);
		}
		else
		{
			pa->num[count].color = WHITE;
		}
		count++;
	}
}
DrawNumUp(pa);

        此时我们在找到要删除的数字。pa->m.rbutton == 1表示右键按下,删除也十分的简单,将对应数组pa->out.str[i][0] = '\0';然后空白填充当前区域。

//鼠标右键删除
void DelMouse(ST* pa)
{
	int i = 0;
	if (pa->m.rbutton == 1)
	{
		for (i = 0; i < 16; i++)
		{
			if (pa->num[i].color != WHITE)
				break;
		}
		pa->out.str[i][0] = '\0';
		pa->num[i].color = WHITE;

		//最后的元素清空
		setfillcolor(WHITE);
		solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		//消除连续点击鼠标
		Sleep(60);
		while (pa->m.lbutton == 1)
		{
			flushmessage();
			pa->m = getmessage();
		}
	}
    //调整删除数字
    Adjust(pa);
}

        当然仅这样,最后删除的地方会留下一片空白,为了美观性,我们在封装一个Adjust(pa);函数将删除元素后面的数字都往前移动一个位置。注意要将原本最后一个数字的位置填充空白。


//调整删除后的数字
void Adjust(ST* pa)
{
	int i = 0;

	for (i = 0; i < pa->out.size; i++)
	{
		if (pa->out.str[i][0] == L'\0')
		{
			//覆盖最后的数字
			pa->num[pa->out.size - 1].color = WHITE;
			setfillcolor(pa->num[pa->out.size - 1].color);
			fillrectangle(pa->num[pa->out.size - 1].x_left, pa->num[pa->out.size - 1].y_left, pa->num[pa->out.size - 1].x_right, pa->num[pa->out.size - 1].y_right);
			FlushBatchDraw();

			for (int j = i; j < pa->out.size - 1; j++)
			{
				Copy(pa->out.str[j], pa->out.str[j + 1]);
			}
			pa->out.str[pa->out.size - 1][0] = L'\0';
			pa->out.size--;
		}
	}

}

      对于一些常用代码可以封装成一个函数方便使用,在这里就不一一列举了。

开始界面

        当做完整个功能的实现后,可以在添加一些开始与结束界面的绘制。这里我们封装一个Welcome();用来输出欢迎界面。

图片输出

        我们可以通过loadimage加载一个图片到变量img中,这是Easyx提供的变量。500,600表示将原来图片压缩到500*600大小,最后再putimage输出图片。它前两个参数是输出图片左上角位置,然后图像按照矩形输出。

        这里的./表示当前目录即.cpp所在的目录,../表示上一级目录

//加载图片
IMAGE img;

loadimage(&img, _T("../../开始界面.jpg"),500,600);
putimage(0, 0, &img);

        然后我们便可以在上面输出一些文字,具体位置要不断尝试调整。

        最后当前页面结束时按下Enter,ExMessage m = getmessage();可以获取外部设备信息,m.vkcode代表虚拟键码虚拟键码 (Winuser.h) - Win32 apps | Microsoft Learn大家可以上官网查看。一个while循环直到读取到Enter按键结束。

//第一层进入
RECT r = { 0, 0,500, 600 };
settextcolor(RED);
setbkmode(TRANSPARENT);//透明文字
settextstyle(40, 0, L"楷体");
drawtext(_T("欢迎使用排序计算器!"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
 r = { 0, 400,500, 600 };
drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
FlushBatchDraw();
ExMessage m = getmessage();
while (m.vkcode != VK_RETURN)
{
	m = getmessage();
}

Sleep(100);//弹起按钮需要时间,程序运行几ms,避免一次确认识别多次

        注意在这了Sleep(100),休眠0.1秒,否则enter可能多次读取并使用。

        接下来第二页用来大于一些使用规范

//第二次规则
putimage(0, 0, &img);
r = {10, 100,500, 200 };
drawtext(_T("1.不要连续快速点击按钮"), &r, DT_LEFT);
r = { 10, 200,500, 300 };
drawtext(_T("2.鼠标左键确认,右键可删除数字"), &r, DT_LEFT| DT_WORDBREAK);
r = {10, 300,500, 400 };
drawtext(_T("3.可识别操作间隔是60ms"), &r, DT_LEFT);
r = {10, 400,500,500 };
drawtext(_T("4.如果1秒后无结果请重新按"), &r, DT_LEFT);

r = { 0, 400,500, 600 };
drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
FlushBatchDraw();

flushmessage();
m.vkcode = 0;
while (m.vkcode != VK_RETURN)
{
	m = getmessage();
}

结束界面

        结束的时候就停留在我们输出完排序后数字的界面,然后按enter结束。

void End(void)
{
	RECT r = { 0, 400,500, 600 };
	drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	//不断检测信息,直到按下enter
	ExMessage m = getmessage();
	while (m.vkcode != VK_RETURN)
	{
		m = getmessage();
	}
	closegraph();
}

结语

        到这里我们的项目算是做完了,整个写下来也是十分的不容易。采用不断封装的方式,我们最终的主函数代码如下

int main()
{
	ST a;//以结构体维护系统

	//欢迎界面
	Welcome();

	//初始化
	init(&a);
	
	while (a.status == OK)
	{
		//跟踪鼠标
		CheckInButton(&a);
		
		//获取点击信息
		Get(&a);

	}

	//结束标识
	End();

	return 0;
}

        我们可以快速的了解整体思路,接下来每个功能只需要具体看某个函数就可以了。 剩下的就是不断规范代码了。

        写到这里十分的不容易,大家点点关注。

        完整代码如下

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include<graphics.h>

/*
	版权所有CSDN 卫胡迪
	喜欢的点点关注
*/
//按钮
struct button
{
	int x_left;
	int y_left;
	int x_right;
	int y_right;
	COLORREF color;
	WCHAR str[100];
}; 



//输出控制
struct out
{
	WCHAR str[20][30] = { 0 };
	int size;
	double strnum[20];
};

//运行状态
enum status
{
	OK,
	START
};

//排序方式
enum sort
{
	UP,
	DOWN
};

//整个界面对象
typedef struct syetem
{
	struct out out;
	struct button arr[16];
	ExMessage m;
	enum status status;
	enum sort sort;
	struct button num[15];
}ST;


//复制字符串
void Copy(WCHAR* des, const WCHAR* src)
{
	while (*des++ = *src++);
}

//求字符串长度
int Len(WCHAR* str)
{
	int sz = 0;
	while (*str++)
	{
		sz++;
	}
	return sz;
}

//将宽字符转为窄字符
void swap(char *des, WCHAR* str)
{
	while (*des++ = (char)*str++);
}

//初始化按钮
void InitButton(ST*pa)
{
	int i1 = 0, i2 = 0, i = 0;
	int count = 0;
	//初始化数字按钮
	for (i1 = 0; i1 < 3; i1++)
	{
		for (i2 = 0; i2 < 5; i2++)
		{
			pa->num[count].x_left = i1 * 170;
			pa->num[count].x_right = 170 + i1 * 170;
			pa->num[count].y_left = i2 * 60;
			pa->num[count].y_right = 60 + i2 * 60;
			pa->num[count].color = WHITE;
			count++;
		}
	}
	//初始化输出上屏字符数组
	struct out* ps = &pa->out;
	ps->size = 0;
	//初始化
	count = 0;
	for (i1 = 0; i1 < 4; i1++)
	{
		for (i2 = 0; i2 < 4; i2++)
		{
			pa->arr[count].x_left = i2 * 125;
			pa->arr[count].x_right = 125+i2*125;
			pa->arr[count].y_left = 300 + 75 * i1;
			pa->arr[count].y_right = 375 + 75 * i1;
			pa->arr[count].color = WHITE;
			count++;
		}
	}

	for (i = 0; i < 3; i++)
	{
		pa->arr[i].str[0] = i + L'1';
		pa->arr[i].str[1] = '\0';
	}
	for (i = 0; i < 3; i++)
	{
		pa->arr[i+4].str[0] = i + L'4';
		pa->arr[i+4].str[1] = '\0';
	}
	for (i = 0; i < 3; i++)
	{
		pa->arr[i + 8].str[0] = i + L'7';
		pa->arr[i + 8].str[1] = '\0';
	}
	//按钮文字
	Copy(pa->arr[3].str, L"确认");
	Copy(pa->arr[7].str, L"升序");
	Copy(pa->arr[11].str, L"降序");
	Copy(pa->arr[15].str, L"开始");
	Copy(pa->arr[14].str, L"删除");
	
	pa->arr[13].str[0] = L'.';
	pa->arr[13].str[1]= '\0';
	pa->arr[12].str[0] = L'0';
	pa->arr[12].str[1] = '\0';
	FlushBatchDraw();
}

//输出按钮文字
void DrawButtonDown(ST *pa)
{
	RECT r = {0,0,0,0 };
	int i = 0;
	for (i = 0; i < 16; i++)
	{
		settextstyle(40, 0, L"楷体");
		RECT r = { pa->arr[i].x_left,pa->arr[i].y_left,pa->arr[i].x_right,pa->arr[i].y_right };
		setfillcolor(pa->arr[i].color);
		fillrectangle(pa->arr[i].x_left, pa->arr[i].y_left, pa->arr[i].x_right, pa->arr[i].y_right);
		drawtext(pa->arr[i].str, &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	}
	
}

//绘画数字
void DrawNumUp(ST* pa)
{
	int i = 0;
	for (i = 0; i < pa->out.size; i++)
	{
		int sz = Len(pa->out.str[i]);
		if (sz > 5)
		{
			settextstyle(170 / sz * 1.8, 0, L"楷体");
		}
		else
		{
			settextstyle(40, 0, L"楷体");
		}

		//设置坐标填充写字
		RECT r = { pa->num[i].x_left,pa->num[i].y_left,pa->num[i].x_right,pa->num[i].y_right };
		setfillcolor(pa->num[i].color);
		fillrectangle(pa->num[i].x_left, pa->num[i].y_left, pa->num[i].x_right, pa->num[i].y_right);
		drawtext(pa->out.str[i], &r, DT_LEFT||DT_SINGLELINE);
	}

	FlushBatchDraw();
}

//画界面线条框
void DrawLine()
{
	//画下方按钮
	line(0, 300, 500, 300);
	line(0, 375, 500, 375);
	line(0, 450, 500, 450);
	line(0, 525, 500, 525);

	line(125, 300, 125, 600);
	line(250, 300, 250, 600);
	line(375, 300, 375, 600);
	//画正上方输出屏
	line(170, 0, 170, 300);
	line(340, 0, 340, 300);
	FlushBatchDraw();
}

//初始化界面
void init(ST *pa)
{
	struct out* ps = &pa->out;
	pa->status = OK;
	//设置输出格式
	initgraph(500, 600);
	setbkcolor(WHITE);
	setlinecolor(BLACK);
	settextcolor(BLACK);

	settextstyle(40,0,L"楷体");
	setbkmode(TRANSPARENT);//透明文字
	setlinestyle(PS_SOLID,3 , 0, 0);
	cleardevice();

	//默认降序
	pa->sort = DOWN;
	FlushBatchDraw();
	//画边框线
	DrawLine();

	//初始化按钮
	InitButton(pa);

	//输出文字与按钮
	DrawButtonDown(pa);
	
	FlushBatchDraw();
}

//检测鼠标在按钮内
void CheckInButton(ST * pa )
{
	//获取鼠标信息
	flushmessage();
	pa->m = getmessage();

	//检测在下方的按钮,在则表示为灰色
	ExMessage m = pa->m;
	int i1 = 0, i2 = 0;
	int count = 0;
	for (i1 = 0; i1 < 4; i1++)
	{
		for (i2 = 0; i2 < 4; i2++)
		{
			if (pa->arr[count].x_left < m.x && m.x < pa->arr[count].x_right
				&& pa->arr[count].y_left<m.y && pa->arr[count].y_right>m.y)
			{
				pa->arr[count].color = RGB(127,127,127);
			}
			else
			{
				pa->arr[count].color = WHITE;
			}
			count++;
		}
	}
	DrawButtonDown(pa);

	//检测上方的数字
	count = 0;
	for (i1 = 0; i1 < 3; i1++)
	{
		for (i2 = 0; i2 < 5; i2++)
		{
			if (pa->num[count].x_left < m.x && m.x < pa->num[count].x_right
				&& pa->num[count].y_left<m.y && pa->num[count].y_right>m.y)
			{
				pa->num[count].color = RGB(127, 127, 127);
			}
			else
			{
				pa->num[count].color = WHITE;
			}
			count++;
		}
	}
	DrawNumUp(pa);

	//刷新缓冲区
	FlushBatchDraw();
}


//排序
int cmpdown(const void* p1, const void* p2)
{

	if (*(double*)p2 > *(double*)p1)
	{
		return 1;
	}
	else
	{
		return -1;
	}

}
int cmpup(const void* p1, const void* p2)
{

	if (*(double*)p1 > *(double*)p2)
	{
		return 1;
	}
	else
	{
		return -1;
	}
}

//数字转字符
void chtonum(WCHAR* str,  double m, ST* pa,int i)
{
	char arr[100] = { 0 };
	int flag = 0;

	//检测有无小数
	if (pa->out.strnum[i] - (int)pa->out.strnum[i] > 0.000001 || pa->out.strnum[i] - (int)pa->out.strnum[i] < -0.000001)
	{
		flag = 1;
	}
	flag==0?sprintf(arr, "%.0lf", m): sprintf(arr, "%.4lf", m);
	char* p = arr;
	while (*str++ = (WCHAR)*p++);
}

//调整删除后的数字
void Adjust(ST* pa)
{
	int i = 0;

	for (i = 0; i < pa->out.size; i++)
	{
		if (pa->out.str[i][0] == L'\0')
		{
			//覆盖最后的数字
			pa->num[pa->out.size - 1].color = WHITE;
			setfillcolor(pa->num[pa->out.size - 1].color);
			fillrectangle(pa->num[pa->out.size - 1].x_left, pa->num[pa->out.size - 1].y_left, pa->num[pa->out.size - 1].x_right, pa->num[pa->out.size - 1].y_right);
			FlushBatchDraw();

			for (int j = i; j < pa->out.size - 1; j++)
			{
				Copy(pa->out.str[j], pa->out.str[j + 1]);
			}
			pa->out.str[pa->out.size - 1][0] = L'\0';
			pa->out.size--;
		}
	}

}

//开始排序
void Start(ST*pa)
{
	//排序
	if (pa->sort == DOWN)
	{
		qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpdown);
	}
	else
	{
		qsort(pa->out.strnum, pa->out.size, sizeof(double), cmpup);
	}
	int i = 0;
	//数字转字符输出
	for (i = 0; i < pa->out.size; i++)
	{
		chtonum(pa->out.str[i], pa->out.strnum[i], pa, i);
	}
	DrawNumUp(pa);
	FlushBatchDraw();
}

//输出最后一个元素
void DrawLast(ST* pa)
{
	int sz = Len(pa->out.str[pa->out.size]);
	if (sz != 0)
	{
		if (sz > 5)
		{
			settextstyle(170 / sz * 1.8, 0, L"楷体");
		}
		else
		{
			settextstyle(40, 0, L"楷体");
		}

		RECT r = { pa->num[pa->out.size].x_left,pa->num[pa->out.size].y_left,pa->num[pa->out.size].x_right,pa->num[pa->out.size].y_right };
		setfillcolor(pa->num[pa->out.size].color);
		fillrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		drawtext(pa->out.str[pa->out.size], &r, DT_LEFT || DT_SINGLELINE);
	}
	line(pa->num[pa->out.size].x_left + 60, pa->num[pa->out.size].y_left + 55, pa->num[pa->out.size].x_left + 100, pa->num[pa->out.size].y_left + 55);
}


//鼠标右键删除
void DelMouse(ST* pa)
{
	int i = 0;
	if (pa->m.rbutton == 1)
	{
		for (i = 0; i < 16; i++)
		{
			if (pa->num[i].color != WHITE)
				break;
		}
		pa->out.str[i][0] = '\0';
		pa->num[i].color = WHITE;

		//最后的元素清空
		setfillcolor(WHITE);
		solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		//消除连续点击鼠标
		Sleep(60);
		while (pa->m.lbutton == 1)
		{
			flushmessage();
			pa->m = getmessage();
		}
	}

	//调整删除数字
	Adjust(pa);
}

//获取鼠标信息
void Get(ST *pa)
{
	int i = 0;
	//检测鼠标按下
	if (pa->m.lbutton == 1)
	{
		//检测鼠标位置
		for (i = 0; i < 16; i++)
		{
			if (pa->arr[i].color != WHITE)
				break;
		}

		if (i!=3 && i!=7 && i!=11 && i!=15 && i!=14)//输入数字
		{
			int sz = Len(pa->out.str[pa->out.size]);
			pa->out.str[pa->out.size][sz++] = pa->arr[i].str[0];
			pa->out.str[pa->out.size][sz] = '\0';
		}
		else if(i==3)//确认
		{
			//字符转数字,便于比较
			char tmp[100] = { 0 };
			swap(tmp, pa->out.str[pa->out.size]);
			sscanf(tmp, "%lf", &pa->out.strnum[pa->out.size]);
			pa->out.size++;
		}
		else if (i == 14)//删除
		{
			pa->out.str[pa->out.size][0] = '\0';
			setfillcolor(WHITE);
			solidrectangle(pa->num[pa->out.size].x_left, pa->num[pa->out.size].y_left, pa->num[pa->out.size].x_right, pa->num[pa->out.size].y_right);
		}
		else if (i == 11)//降序
		{
			pa->sort = DOWN;
		}
		else if (i == 7)//升序
		{
			pa->sort=UP;
		}
		else if (i == 15)//开始
		{
			pa->status = START;
			Start(pa);
		}
		
		//消除连续点击鼠标
		Sleep(60);
		while (pa->m.lbutton == 1)
		{
			flushmessage();
			pa->m= getmessage();
		}
	}
	
	//右键删除
	DelMouse(pa);

	//输出最后输入的数字
	DrawLast(pa);
	
	FlushBatchDraw();
}

void End(void)
{
	RECT r = { 0, 400,500, 600 };
	drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	//不断检测信息,直到按下enter
	ExMessage m = getmessage();
	while (m.vkcode != VK_RETURN)
	{
		m = getmessage();
	}
	closegraph();
}


void Welcome(void)
{
	//创建窗口
	initgraph(500, 600);
	setbkcolor(WHITE);

	//加载图片
	IMAGE img;

	loadimage(&img, _T("../../开始界面.jpg"),500,600);
	putimage(0, 0, &img);

	//第一层进入
	RECT r = { 0, 0,500, 600 };
	settextcolor(RED);
	setbkmode(TRANSPARENT);//透明文字
	settextstyle(40, 0, L"楷体");
	drawtext(_T("欢迎使用排序计算器!"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	 r = { 0, 400,500, 600 };
	drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	FlushBatchDraw();
	ExMessage m = getmessage();
	while (m.vkcode != VK_RETURN)
	{
		m = getmessage();
	}

	Sleep(100);//弹起按钮需要时间,程序运行几ms,避免一次确认识别多次

	//第二次规则
	putimage(0, 0, &img);
	r = {10, 100,500, 200 };
	drawtext(_T("1.不要连续快速点击按钮"), &r, DT_LEFT);
	r = { 10, 200,500, 300 };
	drawtext(_T("2.鼠标左键确认,右键可删除数字"), &r, DT_LEFT| DT_WORDBREAK);
	r = {10, 300,500, 400 };
	drawtext(_T("3.可识别操作间隔是60ms"), &r, DT_LEFT);
	r = {10, 400,500,500 };
	drawtext(_T("4.如果1秒后无结果请重新按"), &r, DT_LEFT);

	r = { 0, 400,500, 600 };
	drawtext(_T("请按Enter继续……"), &r, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
	FlushBatchDraw();

	flushmessage();
	m.vkcode = 0;
	while (m.vkcode != VK_RETURN)
	{
		m = getmessage();
	}
}

int main()
{
	ST a;//以结构体维护系统

	//欢迎界面
	Welcome();

	//初始化
	init(&a);
	
	while (a.status == OK)
	{
		//跟踪鼠标
		CheckInButton(&a);
		
		//获取点击信息
		Get(&a);

	}

	//结束标识
	End();

	return 0;
}

  • 24
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
CruiseYoung提供的带有详细书签的电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 该资料是《Visual C++ 2005入门经典》的源代码及课后练习答案 对应的书籍资料见: Visual C++ 2005入门经典 基本信息 原书名: Ivor Horton's Beginning Visual C++ 2005 原出版社: Wiley 作者: (美)Ivor Horton 译者: 李颂华 康会光 出版社:清华大学出版社 ISBN:9787302142713 上架时间:2007-2-12 出版日期:2007 年1月 开本:16开 页码:1046 版次:1-1 编辑推荐   本书由编程语言先驱者Ivor Horton倾力而著,是国内第一本全面、深入介绍Visual C++ 2005的经典之作! 内容简介   本书系编程语言先驱者Ivor Horton的经典之作,是学习C++编程最畅销的图书品种之一,不仅涵盖了Visual C++ .NET编程知识,还全面介绍了标准C++语言和.NET C++/CLI。本书延续了Ivor Horton讲解编程语言的独特方法,从中读者可以学习Visual C++ 2005的基础知识,并全面掌握在MFC和Windows Forms中访问数据源的技术。此外,本书各章后面的习题将有助于读者温故而知新,并尽快成为C++高效程序员。...    作译者   Ivor Horton是世界著名的计算机图书作家,主要从事与编程相关的顾问及撰写工作,曾帮助无数程序员步入编程的殿堂。他曾在IBM工作多年,能使用多种语言进行编程(在多种机器上使用汇编语言和高级语言),设计和实现了实时闭环工业控制系统。Horton拥有丰富的教学经验(教学内容包括C、C++、Fortran、PL/1、APL等),同时还是机械、加工和电子CAD系统、机械CAM系统和DNC/CNC系统方面的专家。Ivor Horton还著有Beginning Visual C++ 6、Beginning C Programming和Beginning Java 2等多部入门级好书。 目录 封面 -18 前言 -14 目录 -9 第1章 使用Visual C++ 2005编程 1 1.1 .NET Framework 1 1.2 CLR 2 1.3 编写C++应用程序 3 1.4 学习Windows编程 4 1.4.1 学习C++ 4 1.4.2 C++标准 5 1.4.3 控制台应用程序 5 1.4.4 Windows编程概念 6 1.5 集成开发环境简介 7 1.6 使用IDE 9 1.6.1 工具栏选项 9 1.6.2 可停靠的工具栏 10 1.6.3 文档 11 1.6.4 项目和解决方案 11 1.6.5 设置Visual C++ 2005的选项 23 1.6.6 创建和执行Windows应用程序 24 1.6.7 创建Windows Forms应用程序 26 1.7 小结 29 第2章 数据、变量和计算 31 2.1 C++程序结构 31 2.1.1 程序注释 36 2.1.2 #include指令——头文件 37 2.1.3 命名空间和using声明 37 2.1.4 main()函数 38 2.1.5 程序语句 38 2.1.6 空白 40 2.1.7 语句块 41 2.1.8 自动生成的控制台程序 41 2.2 定义变量 42 2.2.1 命名变量 43 2.2.2 C++中的关键字 43 2.2.3 声明变量 44 2.2.4 变量的初值 44 2.3 基本数据类型 45 2.3.1 整型变量 45 2.3.2 字符数据类型 46 2.3.3 整型修饰符 47 2.3.4 布尔类型 48 2.3.5 浮点类型 48 2.3.6 ISO/ANSI C++中的基本类型 49 2.3.7 字面值 50 2.3.8 定义数据类型的同义词 50 2.3.9 具有特定值集的变量 51 2.3.10 指定枚举常量的类型 52 2.4 基本的输入/输出操作 53 2.4.1 从键盘输入 53 2.4.2 到命令行的输出 53 2.4.3 格式化输出 54 2.4.4 转义序列 55 2.5 C++中的计算 57 2.5.1 赋值语句 57 2.5.2 算术运算 58 2.5.3 计算余数 63 2.5.4 修改变量 63 2.5.5 增量和减量运算符 64 2.5.6 计算
CruiseYoung提供的带有详细书签的电子书籍目录 http://blog.csdn.net/fksec/article/details/7888251 该资料是《Visual C++ 2010入门经典(第5版)》的源代码及课后练习答案 对应的书籍资料见: Visual C++ 2010入门经典(第5版) 基本信息 原书名: Ivor Horton's Beginning Visual C++ 2010 原出版社: Wrox 作者: (美)Ivor Horton 译者: 苏正泉 李文娟 出版社:清华大学出版社 ISBN:9787302239994 上架时间:2010-12-20 出版日期:2010 年12月 开本:16开 页码:1011 版次:5-1 编辑推荐   本书针对visual c++ 2010版本做了全面更新,介绍了最新开发环境,讲述了如何使用visual c++构建真实世界的应用程序。    采用了容易理解的讲授方法,并提供了详尽的示例,旨在帮助读者掌握编程技巧 内容简介   作者ivor horton采用了容易理解的讲授方法,并提供了详尽的示例,帮助读者迅速地成为一名优秀的c++编程人员。《visual c++ 2010入门经典(第5版)》针对visual c++ 2010版本进行了全面更新,介绍了最新的开发环境和如何使用visual c++构建现实世界中的应用程序。拥有本书,您就迈向了通往使用两种c++版本编写应用程序的成功之路,并成为一名优秀的c++编程人员。    主要内容    ·使用visual c++ 2010支持的两种c++语言技术讲述c++编程的基础知识    ·分享c++程序的错误查找技术,并介绍通用的调试原则讨论每一个windows应用程序的结构和基本元素    ·举例说明如何使用mfc开发本地windows应用程序    ·指导读者用c++c++/cli设计和创建大量的windows应用程序    ·为帮助读者掌握编程技巧,提供了大量可运行的示例和练习 作译者   Ivor Horton是撰著Java、C和C++编程语言图书的杰出作家之一。大家一致认为,他的著作独具风格,无论是编程新手,还是经验丰富的编程人员,都很容易理解其内容。在个人实践中,Ivor Horton也是一名系统顾问。他从事程序设计教学工作已经超过了25年。   苏正泉,1995年毕业于解放军信息工程学院计算机及应用专业,高级工程师。在IT项目管理、软件开发、系统管理和网络管理方面都有非常丰富的实践经验。曾发表过多篇计算机专业论文,并翻译过多部计算机专业技术书籍。   李文娟,中国石油大学(华东)硕士,现供职于国家行政学院,工作后一直从事软件开发和软件项目管理工作,对计算机语言、计算机体系结构、操作系统都非常熟悉,尤其是精通C和C++编程技术. 目录 封面 -19 封底 -18 扉页 -17 版权 -16 前言 -15 目录 -10 第1章 使用Visual C++ 2010编程 1 1.1 .NET Framework 1 1.2 CLR 2 1.3 编写C++应用程序 3 1.4 学习Windows编程 4 1.4.1 学习C++ 4 1.4.2 C++标准 5 1.4.3 属性 5 1.4.4 控制台应用程序 5 1.4.5 Windows编程概念 6 1.5 集成开发环境简介 7 1.5.1 编辑器 8 1.5.2 编译器 8 1.5.3 链接器 8 1.5.4 库 8 1.6 使用IDE 8 1.6.1 工具栏选项 9 1.6.2 可停靠的工具栏 10 1.6.3 文档 11 1.6.4 项目和解决方案 11 1.6.5 设置Visual C++ 2010的选项 23 1.6.6 创建和执行Windows应用程序 23 1.6.7 创建Windows Forms应用程序 26 1.7 小结 27 1.8 本章主要内容 28 第2章 数据、变量和计算 29 2.1 C++程序结构 29 2.1.1 main()函数 36 2.1.2 程序语句 36 2.1.3 空白 38 2.1.4 语句块 38 2.1.5 自动生成的控制台程序 39 2.2 定义变量 40 2.2.1 命名变量 40 2.2.2 声明变量 41 2.2.3 变量的初始值 42 2.3 基本数据类型 42 2.3.1 整型变量 43 2.3.2 字符数据类型 44 2.3.3 整型修饰符 45 2.3.4 布尔类型 46 2.3.5 浮点类型 46 2.3.6 字面值 47 2.3.7 定义数据类型的同义词 48 2.3.8 具有特定值集的变量 49 2.4 基本的输入/输出操作 50 2.4.1 从键盘输入 50 2.4.2 到命令行的输出 50 2.4.3 格式化输出 51 2.4.4 转义序列 52 2.5 C++中的计算 54 2.5.1 赋值语句 54 2.5.2 算术运算 55 2.5.3 计算余数 59 2.5.4 修改变量 60 2.5.5 增量和减量运算符 60 2.5.6 计算的顺序 63 2.6 类型转换和类型强制转换 64 2.6.1 赋值语句中的类型转换 65 2.6.2 显式类型转换 65 2.6.3 老式的类型强制转换 66 2.7 AUTO关键字 66 2.8 查看类型 67 2.9 按位运算符 67 2.9.1 按位AND运算符 68 2.9.2 按位OR运算符 69 2.9.3 按位EOR运算符 71 2.9.4 按位NOT运算符 71 2.9.5 移位运算符 71 2.10 lvalue和rvalue 73 2.11 了解存储时间和作用域 74 2.11.1 自动变量 74 2.11.2 决定变量声明的位置 76 2.11.3 全局变量 77 2.11.4 静态变量 80 2.12 名称空间 80 2.12.1 声明名称空间 81 2.12.2 多个名称空间 82 2.13 C++/CLI编程 84 2.13.1 C++/CLI特有的基本数据类型 84 2.13.2 命令行上的C++/CLI输出 87 2.13.3 C++/CLI特有的功能—— 格式化输出 88 2.13.4 C++/CLI的键盘输入 91 2.13.5 使用safe_cast 92 2.13.6 C++/CLI枚举 92 2.14 查看C++/CLI类型 96 2.15 小结 97 2.16 练习 97 2.17 本章主要内容 98 第3章 判断和循环 101 3.1 比较数据值 101 3.1.1 if语句 102 3.1.2 嵌套的if语句 104 3.1.3 嵌套的if-else语句 107 3.1.4 逻辑运算符和表达式 109 3.1.5 条件运算符 112 3.1.6 switch语句 113 3.1.7 无条件转移 116 3.2 重复执行语句块 117 3.2.1 循环的概念 117 3.2.2 for循环的变体 119 3.2.3 while循环 126 3.2.4 do-while循环 128 3.2.5 嵌套的循环 129 3.3 C++/CLI编程 132 3.4 小结 137 3.5 练习 138 3.6 本章主要内容 138 第4章 数组、字符串和指针 139 4.1 处理多个相同类型的数据值 139 4.1.1 数组 140 4.1.2 声明数组 140 4.1.3 初始化数组 143 4.1.4 字符数组和字符串处理 144 4.1.5 多维数组 147 4.2 间接数据访问 150 4.2.1 指针的概念 150 4.2.2 声明指针 150 4.2.3 使用指针 152 4.2.4 初始化指针 152 4.2.5 sizeof操作符 158 4.2.6 常量指针和指向常量的指针 159 4.2.7 指针和数组 161 4.3 动态内存分配 168 4.3.1 堆的别名—— 空闲存储器 168 4.3.2 new和delete操作符 168 4.3.3 为数组动态分配内存 169 4.3.4 多维数组的动态分配 171 4.4 使用引用 172 4.4.1 引用的概念 172 4.4.2 声明并初始化lvalue引用 172 4.4.3 声明并初始化rvalue引用 173 4.5 字符串的本地C++库函数 174 4.5.1 查找以空字符结尾的字符串的长度 174 4.5.2 连接以空字符结尾的字符串 174 4.5.3 复制以空字符结尾的字符串 176 4.5.4 比较以空字符结尾的字符串 177 4.5.5 搜索以空字符结尾的字符串 177 4.6 C++/CLI编程 179 4.6.1 跟踪句柄 180 4.6.2 CLR数组 181 4.6.3 字符串 195 4.6.4 跟踪引用 203 4.6.5 内部指针 204 4.7 小结 206 4.8 练习 206 4.9 本章主要内容 207 第5章 程序结构(1) 209 5.1 理解函数 209 5.1.1 需要函数的原因 210 5.1.2 函数的结构 210 5.1.3 使用函数 213 5.2 给函数传递实参 216 5.2.1 按值传递机制 216 5.2.2 给函数传递指针实参 217 5.2.3 给函数传递数组 219 5.2.4 给函数传递引用实参 222 5.2.5 使用const修饰符 224 5.2.6 rvalue引用形参 225 5.2.7 main()函数的实参 227 5.2.8 接受数量不定的函数实参 229 5.3 从函数返回值 231 5.3.1 返回指针 231 5.3.2 返回引用 233 5.3.3 函数中的静态变量 236 5.4 递归函数调用 238 5.5 C++/CLI编程 240 5.5.1 接受数量可变实参的函数 241 5.5.2 main( )的实参 242 5.6 小结 243 5.7 练习 243 5.8 本章主要内容 244 第6章 程序结构(2) 245 6.1 函数指针 245 6.1.1 声明函数指针 246 6.1.2 函数指针作为实参 249 6.1.3 函数指针的数组 250 6.2 初始化函数形参 250 6.3 异常 252 6.3.1 抛出异常 253 6.3.2 捕获异常 254 6.3.3 MFC中的异常处理 255 6.4 处理内存分配错误 256 6.5 函数重载 257 6.5.1 函数重载的概念 258 6.5.2 引用类型和重载选择 260 6.5.3 何时重载函数 260 6.6 函数模板 261 6.7 使用decltype操作符 263 6.8 使用函数的示例 265 6.8.1 实现计算器 265 6.8.2 从字符串中删除空格 268 6.8.3 计算表达式的值 268 6.8.4 获得项值 270 6.8.5 分析数 271 6.8.6 整合程序 274 6.8.7 扩展程序 275 6.8.8 提取子字符串 277 6.8.9 运行修改过的程序 279 6.9 C++/CLI编程 279 6.9.1 理解泛型函数 280 6.9.2 CLR版本的计算器程序 285 6.10 小结 290 6.11 练习 291 6.12 本章主要内容 292 第7章 自定义数据类型 293 7.1 C++中的结构 293 7.1.1 结构的概念 294 7.1.2 定义结构 294 7.1.3 初始化结构 294 7.1.4 访问结构的成员 295 7.1.5 伴随结构的智能感知帮助 298 7.1.6 RECT结构 299 7.1.7 使用指针处理结构 300 7.2 数据类型、对象、类和实例 301 7.2.1 类的起源 303 7.2.2 类的操作 303 7.2.3 术语 303 7.3 理解类 304 7.3.1 定义类 304 7.3.2 声明类的对象 305 7.3.3 访问类的数据成员 305 7.3.4 类的成员函数 307 7.3.5 成员函数定义的位置 309 7.3.6 内联函数 309 7.4 类构造函数 310 7.4.1 构造函数的概念 311 7.4.2 默认的构造函数 312 7.4.3 在类定义中指定默认的形参值 314 7.4.4 在构造函数中使用初始化列表 316 7.4.5 声明显式的构造函数 317 7.5 类的私有成员 318 7.5.1 访问私有类成员 320 7.5.2 类的友元函数 321 7.5.3 默认复制构造函数 323 7.6 this指针 325 7.7 类的const对象 327 7.7.1 类的const成员函数 327 7.7.2 类外部的成员函数定义 328 7.8 类对象的数组 329 7.9 类的静态成员 331 7.9.1 类的静态数据成员 331 7.9.2 类的静态函数成员 334 7.10 类对象的指针和引用 334 7.10.1 类对象的指针 334 7.10.2 类对象的引用 337 7.11 C++/CLI编程 338 7.11.1 定义值类类型 339 7.11.2 定义引用类类型 344 7.11.3 定义引用类类型的复制构造函数 346 7.11.4 类属性 346 7.11.5 initonly字段 358 7.11.6 静态构造函数 360 7.12 小结 360 7.13 练习 360 7.14 本章主要内容 361 第8章 深入理解类 363 8.1 类析构函数 363 8.1.1 析构函数的概念 363 8.1.2 默认的析构函数 364 8.1.3 析构函数与动态内存分配 366 8.2 实现复制构造函数 369 8.3 在变量之间共享内存 370 8.3.1 定义联合 371 8.3.2 匿名联合 372 8.3.3 类和结构中的联合 372 8.4 运算符重载 373 8.4.1 实现重载的运算符 373 8.4.2 实现对比较运算符的完全支持 376 8.4.3 重载赋值运算符 379 8.4.4 重载加法运算符 384 8.4.5 重载递增和递减运算符 387 8.4.6 重载函数调用操作符 388 8.5 对象复制问题 389 8.5.1 避免不必要的复制操作 389 8.5.2 应用rvalue引用形参 392 8.5.3 命名的对象是lvalue 394 8.6 类模板 399 8.6.1 定义类模板 400 8.6.2 根据类模板创建对象 402 8.6.3 使用有多个形参的类模板 405 8.6.4 函数对象模板 406 8.7 使用类 407 8.7.1 类接口的概念 407 8.7.2 定义问题 407 8.7.3 实现CBox类 408 8.8 组织程序代码 425 8.9 字符串的本地C++库类 427 8.9.1 创建字符串对象 427 8.9.2 连接字符串 429 8.9.3 访问与修改字符串 432 8.9.4 比较字符串 436 8.9.5 搜索字符串 439 8.10 C++/CLI编程 447 8.10.1 在值类中重载运算符 447 8.10.2 重载递增和递减运算符 452 8.10.3 在引用类中重载运算符 453 8.10.4 实现引用类型的赋值运算符 455 8.11 小结 456 8.12 练习 456 8.13 本章主要内容 457 第9章 类继承和虚函数 459 9.1 面向对象编程的基本思想 459 9.2 类的继承 460 9.2.1 基类的概念 461 9.2.2 基类的派生类 461 9.3 继承机制下的访问控制 464 9.3.1 派生类中构造函数的操作 467 9.3.2 声明类的保护成员 470 9.3.3 继承类成员的访问级别 473 9.4 派生类中的复制构造函数 474 9.5 友元类成员 477 9.5.1 友元类 479 9.5.2 对类友元关系的限制 479 9.6 虚函数 479 9.6.1 虚函数的概念 481 9.6.2 使用指向类对象的指针 483 9.6.3 使用引用处理虚函数 485 9.6.4 纯虚函数 486 9.6.5 抽象类 487 9.6.6 间接基类 489 9.6.7 虚析构函数 491 9.7 类类型之间的强制转换 494 9.8 嵌套类 495 9.9 C++/CLI编程 498 9.9.1 装箱与拆箱 499 9.9.2 C++/CLI类的继承 499 9.9.3 接口类 505 9.9.4 定义接口类 505 9.9.5 类和程序集 509 9.9.6 被指定为new的函数 513 9.9.7 委托和事件 514 9.9.8 引用类的析构函数和终结器 525 9.9.9 泛型类 527 9.10 小结 536 9.11 练习 536 9.12 本章主要内容 539 第10章 标准模板库 541 10.1 标准模板库的定义 541 10.1.1 容器 542 10.1.2 容器适配器 542 10.1.3 迭代器 543 10.1.4 算法 544 10.1.5 STL中的函数对象 545 10.1.6 函数适配器 545 10.2 STL容器范围 545 10.3 序列容器 545 10.3.1 创建矢量容器 546 10.3.2 矢量容器的容量和大小 549 10.3.3 访问矢量中的元素 553 10.3.4 在矢量中插入和删除元素 553 10.3.5 在矢量中存储类对象 555 10.3.6 排序矢量元素 559 10.3.7 排序矢量中的指针 560 10.3.8 双端队列容器 562 10.3.9 使用列表容器 565 10.3.10 使用其他序列容器 574 10.4 关联容器 588 10.4.1 使用映射容器 589 10.4.2 使用多重映射容器 600 10.5 关于迭代器的更多内容 600 10.5.1 使用输入流迭代器 601 10.5.2 使用插入迭代器 604 10.5.3 使用输出流迭代器 605 10.6 关于函数对象的更多内容 607 10.7 关于算法的更多内容 608 10.7.1 fill() 608 10.7.2 replace() 609 10.7.3 find() 609 10.7.4 transform() 610 10.8 lambda表达式 611 10.8.1 capture子句 612 10.8.2 捕获特定的变量 613 10.8.3 模板和lambda表达式 613 10.8.4 包装lambda表达式 617 10.9 C++/CLI程序的STL 618 10.9.1 STL/CLR容器 619 10.9.2 使用序列容器 619 10.9.3 使用关联容器 627 10.10 C++/CLI中的lambda表达式 633 10.11 小结 633 10.12 练习 633 10.13 本章主要内容 634 第11章 调试技术 635 11.1 理解调试 635 11.1.1 程序故障 636 11.1.2 常见故障 637 11.2 基本的调试操作 638 11.2.1 设置断点 639 11.2.2 设置跟踪点 641 11.2.3 启动调试模式 641 11.2.4 修改变量的值 645 11.3 添加调试代码 645 11.3.1 使用断言 645 11.3.2 添加自己的调试代码 647 11.4 调试程序 652 11.4.1 调用栈 652 11.4.2 单步执行到出错位置 653 11.5 测试扩展的类 656 11.6 调试动态内存 659 11.6.1 检查空闲存储器的函数 660 11.6.2 控制空闲存储器的调试操作 661 11.6.3 空闲存储器的调试输出 662 11.7 调试C++/CLI程序 668 11.7.1 使用调试类Debug和跟踪类Trace 668 11.7.2 在Windows Forms应用程序中获得跟踪输出 676 11.8 小结 677 11.9 本章主要内容 677 第12章 Windows编程的概念 679 12.1 Windows编程基础 679 12.1.1 窗口的元素 680 12.1.2 Windows程序与操作系统 681 12.1.3 事件驱动型程序 682 12.1.4 Windows消息 682 12.1.5 Windows API 682 12.1.6 Windows数据类型 683 12.1.7 Windows程序中的符号 684 12.2 Windows程序的结构 685 12.2.1 WinMain()函数 686 12.2.2 消息处理函数 696 12.2.3 简单的Windows程序 700 12.3 Windows程序的组织 701 12.4 MFC 702 12.4.1 MFC表示法 702 12.4.2 MFC程序的组织方式 702 12.5 使用Windows Forms 706 12.6 小结 707 12.7 本章主要内容 707 第13章 多核编程 709 13.1 并行处理基本知识 709 13.2 并行模式库 710 13.3 并行处理算法 710 13.3.1 使用parallel_for算法 710 13.3.2 使用parallel_for_each算法 712 13.3.3 使用parallel_invoke算法 714 13.4 真正的并行问题 715 13.5 临界区 728 13.5.1 使用critical_section对象 728 13.5.2 锁定代码节或解除代码节锁定 729 13.6 combinable类模板 731 13.7 任务和任务组 733 13.8 小结 736 13.9 练习 736 13.10 本章主要内容 736 第14章 使用MFC编写Windows程序 739 14.1 MFC的文档/视图概念 739 14.1.1 文档的概念 739 14.1.2 文档界面 740 14.1.3 视图的概念 740 14.1.4 链接文档和视图 741 14.1.5 应用程序和MFC 742 14.2 创建MFC应用程序 743 14.2.1 创建SDI应用程序 745 14.2.2 MFC Application Wizard的输出 748 14.2.3 创建MDI应用程序 757 14.3 小结 760 14.4 练习 760 14.5 本章主要内容 760 第15章 处理菜单和工具栏 763 15.1 与Windows进行通信 763 15.1.1 了解消息映射 764 15.1.2 消息类别 767 15.1.3 处理程序中的消息 767 15.2 扩展Sketcher程序 768 15.3 菜单的元素 769 15.4 为菜单消息添加处理程序 771 15.4.1 选择处理菜单消息的类 773 15.4.2 创建菜单消息函数 773 15.4.3 编写菜单消息函数的代码 775 15.4.4 添加更新用户界面的消息处理程序 778 15.5 添加工具栏按钮 781 15.5.1 编辑工具栏按钮的属性 782 15.5.2 练习使用工具栏按钮 783 15.5.3 添加工具提示 784 15.6 C++/CLI程序中的菜单和工具栏 785 15.6.1 理解Windows Forms 785 15.6.2 理解Windows Forms应用程序 786 15.6.3 在CLR Sketcher中添加菜单 788 15.6.4 添加菜单项的事件处理程序 790 15.6.5 实现事件处理程序 791 15.6.6 设置菜单项复选 792 15.6.7 添加工具栏 793 15.7 小结 797 15.8 练习 797 15.9 本章主要内容 797 第16章 在窗口中绘图 799 16.1 窗口绘图的基础知识 799 16.1.1 窗口工作区 800 16.1.2 Windows图形设备界面 800 16.2 Visual C++中的绘图机制 802 16.2.1 应用程序中的视图类 802 16.2.2 CDC类 803 16.3 实际绘制图形 811 16.4 对鼠标进行编程 813 16.4.1 鼠标发出的消息 813 16.4.2 鼠标消息处理程序 814 16.4.3 使用鼠标绘图 816 16.5 练习使用Sketcher程序 837 16.5.1 运行示例 838 16.5.2 捕获鼠标消息 838 16.6 在CLR中绘图 840 16.6.1 在窗体上绘图 840 16.6.2 添加鼠标事件处理程序 840 16.6.3 定义C++/CLI元素类 842 16.6.4 实现MouseMove事件处理程序 850 16.6.5 实现MouseUp事件处理程序 851 16.6.6 实现窗体的Paint事件处理程序 851 16.7 小结 852 16.8 练习 852 16.9 本章主要内容 853 第17章 创建文档和改进视图 855 17.1 创建草图文档 855 17.2 改进视图 859 17.2.1 更新多个视图 859 17.2.2 滚动视图 861 17.2.3 使用MM_LOENGLISH映射模式 865 17.3 删除和移动形状 866 17.4 实现上下文菜单 866 17.4.1 关联菜单和类 867 17.4.2 练习弹出菜单 870 17.4.3 突出显示元素 870 17.4.4 处理菜单消息 874 17.5 处理屏蔽的元素 881 17.6 扩展CLR Sketcher 882 17.6.1 坐标系统转换 882 17.6.2 定义草图类 885 17.6.3 在Paint事件处理程序中绘制草图 886 17.6.4 实现元素的突出显示 887 17.6.5 创建上下文菜单 891 17.7 小结 897 17.8 练习 897 17.9 本章主要内容 898 第18章 使用对话框和控件 899 18.1 理解对话框 899 18.2 理解控件 900 18.3 创建对话框资源 900 18.3.1 给对话框添加控件 901 18.3.2 测试对话框 902 18.4 对话框的编程 902 18.4.1 添加对话框类 902 18.4.2 模态和非模态对话框 903 18.4.3 显示对话框 903 18.5 支持对话框控件 906 18.5.1 初始化控件 906 18.5.2 处理单选按钮消息 907 18.6 完成对话框的操作 908 18.6.1 给文档添加线宽 908 18.6.2 给元素添加线宽 909 18.6.3 在视图中创建元素 910 18.6.4 练习使用对话框 910 18.7 使用微调按钮控件 911 18.7.1 添加Scale菜单项和工具栏按钮 911 18.7.2 创建微调按钮 911 18.7.3 生成比例对话框类 913 18.7.4 显示微调按钮 915 18.8 使用缩放比例 916 18.8.1 可缩放的映射模式 916 18.8.2 设置文档的大小 917 18.8.3 设置映射模式 918 18.8.4 同时实现滚动与缩放 919 18.9 使用CTaskDialog类 921 18.9.1 显示任务对话框 921 18.9.2 创建CTaskDialog对象 923 18.10 使用状态栏 925 18.11 使用列表框 929 18.11.1 删除比例对话框 929 18.11.2 创建列表框控件 929 18.12 使用编辑框控件 931 18.12.1 创建编辑框资源 931 18.12.2 创建对话框类 933 18.12.3 添加Text菜单项 934 18.12.4 定义文本元素 935 18.12.5 实现CText类 935 18.13 CLR Sketcher中的对话框和控件 940 18.13.1 添加对话框 940 18.13.2 创建文本元素 946 18.14 小结 953 18.15 练习 953 18.16 本章主要内容 953 第19章 存储和打印文档 955 19.1 了解序列化 955 19.2 序列化文档 956 19.2.1 文档类定义中的序列化 956 19.2.2 文档类实现中的序列化 957 19.2.3 基于CObject的类的功能 959 19.2.4 序列化的工作方式 960 19.2.5 如何实现类的序列化 961 19.3 应用序列化 961 19.3.1 记录文档修改 962 19.3.2 序列化文档 963 19.3.3 序列化元素类 965 19.4 练习序列化 968 19.5 打印文档 969 19.6 实现多页打印 972 19.6.1 获取文档的总尺寸 973 19.6.2 存储打印数据 973 19.6.3 准备打印 974 19.6.4 打印后的清除 976 19.6.5 准备设备上下文 976 19.6.6 打印文档 977 19.6.7 获得文档的打印输出 980 19.7 CLR Sketcher中的序列化和打印 981 19.7.1 了解二进制序列化 981 19.7.2 序列化草图 985 19.7.3 打印草图 995 19.8 小结 996 19.9 练习 996 19.10 本章主要内容 997 第20章 编写自己的DLL 999 20.1 了解DLL 999 20.1.1 DLL的工作方式 1000 20.1.2 DLL的内容 1003 20.1.3 DLL变体 1003 20.2 决定放入DLL的内容 1004 20.3 编写DLL 1005 20.4 小结 1011 20.5 练习 1011 20.6 本章主要内容 1011 前言   欢迎使用本书。通过学习本书,您可以使用Microsoft公司最新的应用程序开发系统,成为优秀的C++程序员。本书旨在讲述C++程序设计语言,然后讲述如何运用C++语言开发自己的Windows应用程序。在此过程中,读者将了解这一最新Visual C++版本所提供的很多激动人心的新功能,包括如何在自己的应用程序中充分利用多核处理器。   0.1 使用C++语言编程   Visual C++ 2010支持两种截然不同但又紧密相关的C++语言,即ISO/IEC标准C++(本书称其为本地C++)和C++/CLI。虽然很多专业开发人员选用本地C++,尤其是当性能是需要考虑的主要因素时,但是C++/CLI和Windows Forms应用程序带来的开发速度和简易性使得C++/CLI也成了基本的语言。因此,本书将深入讨论这两种版本的C++语言。   Visual C++ 2010完全支持原来的ISO/IEC标准C++语言,同时还支持即将发布的ISO/IEC标准C++提供的一些功能强大的新特性。因此,本书不仅涵盖ISO/IEC标准C++的原有功能,同时还会介绍新语言特性。   Visual C++ 2010也支持C++/CLI,它是Microsoft公司作为本地C++的扩展而开发C++版本。C++/CLI背后的思想是向本地C++添加一些特性,从而能够开发以.NET支持的虚拟机环境为目标的应用程序。这就将C++添加到能使用.NET Framework的其他语言(例如,BASIC和C#)中。C++/CLI语言目前是一个ECMA标准,同时也符合定义.NET虚拟机环境的CLI标准。   Visual C++ 2010的这两种C++版本互为补充,各自完成不同的任务。ISO/IEC C++用于开发在本地计算机上运行的高性能应用程序,而C++/CLI专门为.NET Framework开发应用程序。掌握了使用这两种C++版本开发应用程序的基础知识之后,就能够充分利用Visual C++ 2010。   0.2 开发Windows应用程序   充分理解C++之后,就可以着手开发Windows应用程序。Microsoft基本类(Microsoft Foundation Classes,MFC)封装了Windows API,提供了全面而易于使用的功能,从而能够使用本地C++开发高性能的Windows应用程序。   当编写本地C++程序时,可以从自动生成的代码中获得大量帮助,但仍然需要亲自编写大量C++代码。我们不仅需要对面向对象编程(OOP)技术有扎实的理解,而且需要充分了解Windows编程所涉及的各个方面。本书会介绍所有这些知识点。   C++/CLI虽然针对.NET Framework开发,但同时也是Windows Forms应用程序开发的载体。开发Windows Forms应用程序时,在不用编写一行代码的情况下,即使不能创建应用程序交互所需的用户界面的所有元素,也可以创建其中的很多元素。当然,仍然需要定制Windows Forms应用程序,才能完成相应的任务,但开发时间与使用本地C++创建应用程序相比只占一小部分。当给Windows Forms应用程序添加定制代码时,即使这部分代码只占到代码总量的很小比例,也仍然要求我们深入理解C++/CLI语言,才能做到游刃有余。本书旨在介绍这些知识。   0.3 高级库功能   并行模式库(Parallel Patterns Library,PPL)是Visual C++ 2010增加的一个令人激动的新功能,通过此功能,我们可以轻松编写使用多处理器的程序。在过去,为多处理器编程并非易事,但有了PPL,这就确实变得很容易了。本书将介绍PPL的各种使用方式,从而加快计算密集型应用程序的执行速度。   0.4 本书读者对象   本书针对任何想要学习如何使用Visual C++ 2010编写在Microsoft Windows操作系统下运行的C++应用程序的读者。阅读本书不需要预先具备任何特定编程语言的知识。如果属于下列4种情形之一,您就适合学习本教程:   ·属于编程新手,十分渴望投入编程世界,并最终掌握C++。要取得成功,您至少需要对计算机的工作原理有大体的理解——包括内存的组织方式以及数据和指令的存储方式。   ·具备一些其他语言的编程经验,如BASIC;渴望学习C++,并想提升实际的Microsoft Windows编程技能。   ·有一些使用C语言C++语言的经验,但使用环境不是Microsoft Windows;希望使用最新的工具和技术,扩展在Windows环境下编程的技能。   ·有一些C++知识,并希望扩展C++技能,成为会使用C++/CLI的编程人员。   0.5 本书主要内容   本书实质上涵盖了两大主题:C++编程语言以及如何使用MFC或.NET Framework编写Windows应用程序。在开发完全成熟的Windows应用程序之前,需要具备相当水平的C++知识,因此,首先学习这本C++教程。 .  本书的第一部分通过可运行于两种C++语言版本上的一个详细的循序渐进式教程,讲授了使用Visual C++ 2010支持的两种C++语言技术编写C++程序的基础知识。您将了解本地ISO/IEC C++语言的语法和用法,并通过一系列范围广泛的可工作示例,获得实际运用它的经验和信心。本书也提供了一些练习,可以检验所学的知识,并且可以下载练习题答案。而C++/CLI作为本地C++的扩展来学习,这仍然是通过一些可运行的示例来说明每一个特性的工作原理。   当然,本语言教程也介绍和说明了C++标准库功能的用法,因为开发程序时极有可能使用它们。随着深入地学习C++语言,您的标准库知识会不断增加。还将学习标准模板库(Standard Template Library,STL)以两种形式——即本地C++版本和C++/CLI版本——提供的强大工具。另外,本书还用一章的篇幅专门讲述新增的并行模式库(PPL)功能,从而能够利用PC的多核处理功能来开发计算密集型应用程序。   对C++的运用有信心之后,就可以继续学习Windows编程了。通过创建超过2000行代码的大型可运行的应用程序,学习如何使用MFC来开发本地Windows应用程序。开发此应用程序贯穿多章内容,使用到了MFC提供的一系列用户界面功能。为学习如何使用C++/CLI编写Windows程序,相应地开发一个与本地C++应用程序具有相似用户界面特性的Windows Forms应用程序。   0.6 本书结构   本书内容的结构安排如下:   ·第1章介绍使用C++编写本地应用程序和.NET Framework应用程序所需要理解的基本概念,以及在Visual C++ 2010开发环境中体现的主要思想,还叙述了如何使用Visual C++ 2010的功能来创建本书其余部分要学习的各种C++应用程序。   ·第2~9章讲授两种C++语言版本。第2~9章内容的组织方式都相似:各章的前半部分讨论本地C++语言的元素,后半部分讨论如何在C++/CLI中提供相同的功能。   ·第10章介绍如何使用标准模板库(Standard Template Library,STL)。STL是一组功能强大且全面的工具,用来组织和操作本地C++程序中的数据。由于STL是独立于应用程序的,因此可以在上下文中大量应用它。第10章还介绍了Visual C++ 2010新增的STL/CLR。它是C++/CLI应用程序的STL版本。   ·第11章介绍了在C++程序中查找错误的技术。涵盖了调试程序的一般原则,以及Visual C++ 2010提供的基本特性,这些特性可以帮助我们查找代码中的错误。   ·第12章讨论Microsoft Windows应用程序的组织方式,并描述和展示了在所有Windows应用程序中都存在的基本元素。本章解释了以本地C++语言编写的、使用Windows API和MFC的Windows应用程序示例,还给出了一个使用C++/CLI语言编写的Windows Forms应用程序的基础示例。   ·第13章介绍了如何在PC有多核处理器的情况下编写程序以使用多个处理器。通过一些完整的工作示例展示了并行处理的基本技术,这些示例Windows API应用程序是计算密集型程序。   ·第14~19章讲述Windows编程。详细描述了如何使用MFC提供的构建GUI的功能编写本地C++ Windows应用程序以及如何在C++/CLI Windows应用程序中使用.NET Framework。我们将学习如何创建并使用通用控件来构建应用程序的图形用户界面,还将学习如何处理因用户与程序的交互作用而产生的事件。除了学习构建GUI的技术以外,还将从开发该应用程序的过程中学到如何打印文档,以及如何在磁盘上保存应用程序数据。   ·第20章讲述为使用MFC创建自己的库而需要知道的基本知识。我们将了解可以创建的不同种类的库,还将开发能够与前6章开发的应用程序协同工作的示例。   本书各章内容都包括许多工作示例,通过这些示例阐明所讨论的编程技术。每章结束时都总结了该章所讲述的要点,大多数章节都在最后给出了一组练习,您可以应用所学的技术来试着解答这些练习。练习的答案连同书中的所有代码都可以从http://www.wrox.com和http://www.tupwk.com.cn/ downpage下载。关于C++语言教程使用的示例都是使用简单的命令行输入和输出的控制台程序。这种方法使我们能够在不陷入复杂的Windows GUI编程的情况下,学习C++的各种功能。实际上,只有在透彻地理解编程语言之后,才能进行Windows 编程。   如果希望使学习过程尽可能简单,或者如果您是程序设计初学者,那么最初可以只学习本地C++编程语言。讲授C++语言的各章(第2~9章)都是首先讨论本地C++功能的特定方面,然后再讨论C++/CLI在相同的上下文中引入的新功能。以这种方式组织各章内容的原因在于,C++/CLI是作为ISO/IEC标准语言的扩展定义的,对C++/CLI的理解是以对ISO/IEC C++的理解为基础的。因此,您可以只阅读各章中的本地C++部分,而忽略后面的C++/CLI部分。然后可以继续使用本地C++开发Windows应用程序,而免去记住两种语言版本的苦恼。在熟悉了ISO/IEC C++之后,您可以回头重新学习C++/CLI。当然,如果您已经有一些编程经验,也可以逐章进行学习,从而同步增加这两种C++语言版本的知识。   0.7 使用本书的前提   为了充分地使用本书,需要可支持MFC的某个Visual C++ 2010(或Visual Studio 2010)版本。需要注意的是,免费的Visual C++ 2010 Express Edition版本是不行的。因为此版本只提供C++编译器以及对基本Windows API的访问,并没有提供MFC库。因此,Visual C++ 2010(或Visual Studio 2010)的任何付费版本都能够编译并执行本书的所有示例。   0.8 源代码   读者在阅读本书提供的代码时,既可以亲自输入所有代码,也可以使用随书提供的代码文件。本书所有代码均可以从http://www.wrox.com/或www.tupwk.com.cn/downpage网站下载。进入该网站后,读者可以根据本书的书名查找本书(既可以使用搜索框,也可以使用书名列表进行查找),然后单击本书详细内容页面上提供的Download Code链接,就可以下载本书提供的所有代码。   注意:   由于许多书籍名称与本书类似,读者也可以通过ISBN进行查找,本书的ISBN为:978-0-470-50088-0。   另外,读者可以从前面提到的CodePlex网站下载本书或其他Wrox书籍的代码,也可以从Wrox的代码下载页面http://www.wrox.com/dynamic/books/download.aspx和http://www. tupwk.com.cn/downpage下载本书或其他Wrox书籍的代码。   源代码下载成功后,读者用任一解压工具将其解压即可。   0.9 勘误表   为了避免本书文字和代码中存在错误,我们已经竭尽全力。然而,世界上并不存在完美无缺的事物,所以本书可能仍然存在错误。如果读者在我们编写的某本书籍中发现了诸如拼写错误或代码缺陷等问题,那么请告诉我们,我们对此表示感谢。利用勘误表反馈错误信息,可以为其他读者节省大量时间,同时,我们也能够受益于读者的帮助,这样有助于我们编写出质量更高的专业著作。   如果读者需要参考本书的勘误表,请在网站http://www.wrox.com中用搜索框或书名列表查找本书书名。然后,在本书的详细内容页面上,单击Book Errata链接。在随后显示的页面中,读者可以看到与本书相关的所有勘误信息,这些信息是由读者提交、并由Wrox的编辑们加上的。通过访问http://www.wrox.com/misc-pages/booklist.shtml,读者还可以看到Wrox出版的所有书籍的勘误表。   如果读者没有在Book Errata页面上找到自己发现的错误,那么请转到页面http://www. wrox.com/contact/techsupport.shtml,针对您所发现的每一项错误填写表格,并将表格发给我们,我们将对表格内容进行认真审查,如果确实是我们书中的错误,我们将在该书的Book Errata页面上标明该错误信息,并在该书的后续版本中改正。   0.10 关于p2p.wrox.com网站   如果读者希望能够与作者进行讨论,或希望能够参与到读者的共同讨论中,那么请加入p2p.wrox.com论坛。该论坛是一个基于Web的系统,读者可以在论坛发表与Wrox出版的书籍及相关技术的信息,并与其他读者和技术用户进行讨论。论坛提供了订阅功能,可以将与读者所选定主题相关的新帖子定期发送到读者的电子邮箱。Wrox的作者、编辑、业界专家,以及其他读者都会参与论坛中的讨论。   读者可以在http://p2p.wrox.com参与多个论坛的讨论,这些论坛不仅能够帮助读者更好地理解本书,还有助于读者更好地开发应用程序。如果读者希望加入论坛,那么请按照以下步骤执行:   (1) 进入http://p2p.wrox.com页面,单击Register链接。   (2) 阅读使用条款,然后单击Agree按钮。   (3) 填写必要的信息及可选信息,然后单击Submit按钮。   (4) 随后读者会收到一封电子邮件,邮件中说明了如何验证账户并完成整个加入过程。   读者无须加入P2P论坛即可阅读论坛消息,但如果需要发表主题或发表回复,那么必须加入论坛。   成功加入论坛后,读者就可以发表新主题了。此时,读者还可以回复其他用户发表的主题。读者在任何时间都可以阅读论坛信息,如果需要论坛将新的信息发送到自己的电子邮箱,那么可以单击论坛列表中论坛名称旁的Subscribe to this Forum图标完成这项功能设置。   如果读者需要获得更多与Wrox P2P相关的信息,请阅读P2P FAQs,这样可以获得大量与P2P和Wrox出版的书籍相关的具体信息。阅读FAQs时,请单击P2P页面上的FAQs链接。   
密码学是一门研究如何保护信息安全的学科,它涵盖了密码算法的设计、分析和应用,以及信息的加密、解密和认证等相关技术。在计算机科学的领域中,密码学起到了至关重要的作用。 C/C++编程语言是一种常用的编程语言,具有灵活性和高效性。在密码学领域中,C/C++语言也广泛应用于密码算法的实现。 百度作为我国最大的互联网公司之一,为了保护用户的隐私和数据安全,很可能使用了密码学技术来加密用户敏感信息。百度的密码学实现可能涉及到许多领域,例如网络通信中的加密算法、用户身份认证以及数据存储和传输的加密保护等。 在C/C++语言中,实现百度的密码学需要先选择合适的密码算法,如对称密码算法(如AES、DES)或非对称密码算法(如RSA、ECC),然后使用C/C++语言提供的库函数进行编写。 在具体实现上,可以使用C/C++的位运算、数组操作等特性,来完成密码算法中的加密、解密和认证过程。同时,C/C++语言也可以方便地调用操作系统提供的API接口,以实现与其他系统的安全通信。 总之,密码学在C/C++语言中的实现是一项重要的任务,需要密切结合密码学理论和C/C++编程技术,以保证信息的安全性和可靠性。对于百度这样的大型互联网公司来说,密码学的实现是保护用户数据安全的基础,也是其在互联网领域中赖以发展和壮大的重要一环。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值