控制台重定向

  • 结构WNDCLASS包含一个窗口类的全部信息,也是Windows编程中使用的基本数据结构之一,应用程序通过定义一个窗口类确定窗口的属性
    基本方法有:
 typedef struct _WNDCLASS {
	UINT style;// 窗口类型
	WNDPROC lpfnWndProc;//窗口处理函数
	int cbClsExtra;//窗口扩展
	int cbWndExtra;//窗口实例扩展
	HINSTANCE hInstance;//实例句柄
	HICON hIcon;//窗口的最小化图标
	HCURSOR hCursor;//窗口鼠标光标
	HBRUSH hbrBackground;//窗口背景色
	LPCTSTR lpszMenuName;//窗口菜单
	LPCTSTR lpszClassName;// 窗口类名
} WNDCLASS, *LPWNDCLASS;

  • int WINAPI WinMain:正如在C程序中的进入点是函数main一样,Windows程序的进入点是WinMain
 int WINAPI WinMain(
	HINSTANCE hInstance,//当前实例句柄
	HINSTANCE hPrevInstance,//先前实例句柄
	LPSTR lpCmdLine,//命令行参数
	int nCmdShow//显示状态(最大化、最小化、隐藏)
);

  • WNDCLASS中的回调函数是窗体的消息处理函数:LRESULT CALLBACK
    LRESULT//表示会返回多种long型值
    CALLBACK//只是为了识别这是一个回调函数的空宏,回调函数,这里也叫窗口函数,是给windows系统调用的
    一个消息由一个消息名称(UINT),和两个参数(WPARAM,LPARAM) 组成
LRESULT CALLBACK WndProc(
	HWND hwnd, //窗口句柄,整型值
	UINT msg, //unsigned int,消息名称
	WPARAM wParam,//typedef UINT WPARAM;control identifier
	LPARAM lParam//typedef LONG LPARAM;notification messages
);

  • cbClsExtra和cbWndExtra主要区别:
  1. 两者都是附加额外空间;
  2. cbClsExtra是对类的(这里所说的类不是C++中的类,而是WNDCLASS结构的变量用RegisterClass注册后,在系统中记录的一组信息,是窗口的一个模板),用该类生成的所有窗口共享该附加空间。类似于C++类中的static变量。
  3. cbWndExtra是对窗口的,每实例化一个窗口都有这么一个附加空间。类似于C++类中的成员变量

  • LoadIcon:用于加载窗口图标;该函数从与 hInstance 模块相关联的可执行文件中装入lpIconName指定的图标资源,仅当图标资源还没有被装入时该函数才执行装入操作,否则只获取装入的资源句柄。
    比如,在对窗口类初始化时,我们可以如下使用:
WNDCLASS wndclass;
wndclass.hIcon = LoadIcon(NULL,IDI_APPLICATION);
  • 上面代码中,我们将标题栏上的图标定义为 IDI_APPLICATION.
    注意,上面函数中第一个参数为NULL,此时使用的是系统预定义的图标,其它预定义选项可参考 MSDN;也可以使用自定义的图标资源,即使用 MAKEINTRESOURCE 宏对一个十六位数的资源标识符(高 8 位为0,低 8位为图标资源ID)进行转换
 LoadIcon(
	 HINSTANCE hInstance,
	 LPCSTR lpIconName
 );

HCURSOR LoadCursor(
	HINSTANCE hlnstance,//标识一个模块事例,它的可执行文件包含要载入的光标
	LPCTSTR lpCursorName//指向以NULL结束的字符串的指针,该字符串存有等待载入的光标资源名
 );
  • 要使用Win32预定义的一个光标,应用程序必须把hlnstance参数设为NULL,并把IpCursorName设为如下值之一:
    IDC_APPSTARTING 标准的箭头和小沙漏
    IDC_ARROW 标准的箭头
    IDC_CROSS 十字光标
    IDC_HAND Windows 98/Me, Windows 2000/XP: Hand
    IDC_HELP 标准的箭头和问号
    IDC_IBEAM 工字光标
    IDC_ICON Obsolete for applications marked version 4.0 or later.
    IDC_NO 禁止圈
    IDC_SIZE Obsolete for applications marked version 4.0 or later. Use IDC_SIZEALL.
    IDC_SIZEALL 四向箭头指向东、西、南、北
    IDC_SIZENESW 双箭头指向东北和西南
    IDC_SIZENS 双箭头指向南北
    IDC_SIZENWSE 双箭头指向西北和东南
    IDC_SIZEWE 双箭头指向东西
    IDC_UPARROW 垂直箭头
    IDC_WAIT 沙漏,Windows7系统下会显示为选择的圆圈表示等待

  • RegisterClass():注册窗口类;一个窗口类只有被注册了,才可以被CreateWindow等函数调用,wndclass是一个描述要注册的窗口类的信息的结构体;
if(0==RegisterClass(&wndClass))
		return 0;//注册窗口类并判断注册是否成功

  • ShowWindow():该函数设置指定窗口的显示状态
 BOOL ShowWindow(
	 HWND hWnd, //窗口句柄
	 int nCmdShow//指定窗口如何显示,该参数可以为值(0~11)之一
 );

  • GetMessage():从调用线程的消息队列里取得一个消息并将其放于指定的结构,获取消息成功后,线程将从消息队列中删除该消息;在创建窗口、显示窗口、更新窗口后,我们需要编写一个消息循环,不断地从消息队列中取出消息,并进行响应。要从消息队列中取出消息,我们需要调用GetMessage()函数,该函数的原型声明如下
 GetMessage(
	 LPMSG lpMsg,//指向MSG结构的指针,GetMessage从线程的消息队列中取出的消息信息将保存在该结构体对象中
	 HWND hWnd,//取得其消息的窗口的句柄,指定接收属于哪一个窗口的消息;通常我们将其设置为NULL,用于接收属于调用线程的所有窗口的窗口消息
	 UINT wMsgFilterMin,//指定被检索的最小消息值的整数
	 UINT wMsgFilterMax//指定被检索的最大消息值的整数;如果wMsgFilterMin和wMsgFilter Max都设置为0,则接收所有消息
 );
 while(GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);//将虚拟键消息转换为字符消息。字符消息被投递到调用线程的消息队列中,当下一次调用GetMessage函数时被取出
		DispatchMessage(&msg);//分派一个消息到窗口过程,由窗口过程函数对消息进行处理
	}
  • GetMessage函数只有在接收到WM_QUIT消息时,才返回0。此时while语句判断的条件为假,循环退出,程序才有可能结束运行;在没有接收到WM_QUIT消息时,Windows应用程序就通过这个while循环来保证程序始终处于运行状态
  • Windows应用程序的消息处理过程如下:
  • 操作系统接收到应用程序的窗口消息,将消息投递到该应用程序的消息队列中。
  • 应用程序在消息循环中调用GetMessage函数从消息队列中取出一条一条的消息。取出消息后,应用程序可以对消息进行一些预处理,例如,放弃对某些消息的响应,或者调用TranslateMessage产生新的消息。
  • 应用程序调用DispatchMessage,将消息回传给操作系统。消息是由MSG结构体对象来表示的,其中就包含了接收消息的窗口的句柄。因此,DispatchMessage函数总能进行正确的传递。
  • 系统利用WNDCLASS结构体的lpfnWndProc成员保存的窗口过程函数的指针调用窗口过程,对消息进行处理(即“系统给应用程序发送了消息”)。

  • 窗口消息
    WM_CREATE:是windows中一个窗口消息;当一个应用程序通过CreateWindowEx函数或者CreateWindow函数请求创建窗口时发送此消息,(此消息在函数返回之前发送)。
    WM_PAINT:当窗口显示区域的一部分显示内容或者全部变为“无效”,以致于必须“更新画面”时,将由这个消息通知程序。
    WM_CHAR:WM_CHAR消息包含被按下键盘按键的字符编码
    WPARAM wParam //包含按的什么键;LPARAM lParam //一些附加信息,如在按键的同时是否按了Ctrl、Alt、Shift键
    WM_CLOSE:窗口或应用程序应该关闭的时收到此消息;结合DestroyWindow函数使用,销毁指定窗口
    WM_DESTROY:窗口销毁后,即调用DestroyWindow()后,消息队列得到的消息;结合PostQuitMessage函数使用,通知系统当前有一个线程发送了进程中止退出请求;PostQuitMessage函数投递一个WM_QUIT消息到线程消息队列并且立即返回,该函数简单的通知系统线程请求马上退出。当线程从它的消息队列收到WM_QUIT消息时,将退出自身消息循环并且交还控制给操作系统;其中0将作为WM_QUIT消息的wParam参数,作为程序退出码返回给系统处理
    DefWindowProc()默认的窗口处理函数,我们可以把不关心的消息都丢给它来处理;例如,当这个函数在处理关闭窗口消息WM_CLOSE时,是调用DestroyWindow函数关闭窗口并且发WM_DESTROY消息给应用程序;而它对WM_DESTROY这个消息是不处理的,因为我们在应用程序中对这个消息的处理是发出WM_QUIT消息。因此WM_CLOSE、WM_DESTROY、WM_QUIT这三个消息是先后产生的

  • BeginPaint():函数为指定窗口进行绘图工作的准备,并将和绘图有关的信息填充到一个PAINTSTRUCT结构中
 HDC BeginPaint(
	HWND hwnd, //[输入]被重绘的窗口句柄
	LPPAINTSTRUCT lpPaint //[输出]指向一个用来接收绘画信息的PAINTSTRUCT结构
);
  • 如果函数成功,返回值是指定窗口的“显示设备描述表”句柄;hDC是设备场景句柄;hDC与绘图API(GDI函数)有关,是把窗口绘制在屏幕上用的,例:hDC=BeginPaint(hWnd,&paintStruct);

  • TextOut():该函数用当前选择的字体、背景颜色和正文颜色将一个字符串写到指定位置;
 BOOL TextOut(
	HDC hdc, // 设备描述表句柄
	int nXStart, // 字符串的开始位置 x坐标
	int nYStart, // 字符串的开始位置 y坐标
	LPCTSTR lpString, // 字符串
	int cbString // 字符串中字符的个数
);

  • GetClientRect():该函数获取窗口客户区的大小;注意:窗口的客户区为窗口中除标题栏、菜单栏之外的地方
BOOL GetClientRect(
	HWND hWnd, // 窗口句柄
	LPRECT lpRect // 客户区坐标
);
  • RECT:这个对象是用来存储成对出现的参数,比如,一个矩形框的左上角坐标、宽度和高度,RECT结构通常用于Windows编程。

  • DrawText():该函数在指定的矩形里写入格式化的正文,根据指定的方法对正文格式化(扩展的制表符,字符对齐、折行等)
 int DrawText(
	HDC hDC, // [输入]设备环境句柄
	LPCTSTR lpString, // [输入]指向将被写入的字符串的指针
	int nCount, // [输入]指向字符串中的字符数。如果nCount为-1,则lpString指向的字符串被认为是以\0结束的,DrawText会自动计算字符数。
	LPRECT lpRect, // [输入/输出]指向结构RECT的指针,其中包含文本将被置于其中的矩形的信息(按逻辑坐标)
	UINT uFormat // [输入]指定格式化文本的方法
);

  • ZeroMemory():其作用是用0来填充一块内存区域
 void ZeroMemory( 
	 PVOID Destination,//指向一块准备用0来填充的内存区域的开始地址
	 SIZE_T Length//准备用0来填充的内存区域的大小,按字节来计算
 );

  • SECURITY_ATTRIBUTES:安全属性结构体,包含一个对象的安全描述符,并指定检索到指定这个结构的句柄是否是可继承的;该结构的lpSecurityDescriptor成员用于设定管道的安全属性,如果传NULL,那么该管道将获得一个默认的安全属性
 typedef struct _SECURITY_ATTRIBUTES {
	DWORD nLength; //结构体的大小,可用SIZEOF取得
	LPVOID lpSecurityDescriptor; //安全描述符
	BOOL bInheritHandle ;//安全描述的对象能否被新创建的进程继承
} SECURITY_ATTRIBUTES,* PSECURITY_ATTRIBUTES;

  • CreatePipe()创建一个匿名管道,并从中得到读写管道的句柄
 BOOL WINAPI CreatePipe( 
	 _Out_PHANDLE hReadPipe, //返回一个可用于读管道数据的文件句柄
	 _Out_PHANDLE hWritePipe, //返回一个可用于写管道数据的文件句柄
	 _In_opt_LPSECURITY_ATTRIBUTES lpPipeAttributes,//传入一个SECURITY_ATTRIBUTES结构的指针,该结构用于决定该函数返回的句柄是否可被子进程继承。如果传NULL,则返回的句柄是不可继承的
	 _In_DWORD nSize//管道的缓冲区大小。但是这仅仅只是一个理想值,系统根据这个值创建大小相近的缓冲区。如果传入0 ,那么系统将使用一个默认的缓冲区大小
  );

  • STARTUPINFO指定新进程的主窗口特性的一个结构
    DWORD cb; //包含STARTUPINFO结构中的字节数,将cb初始化为sizeof(STARTUPINFO)
    DWORD dwFlags; //使用标志及含义,STARTF_USESHOWWINDOW:使用wShowWindow成员;STARTF_USESTDHANDLES:使用hStdInput、hStdOutput和hStdError成员
    wShowWindow的设置取决与同数据结构中的dwFlags, 如果dwFlags 被设置成STARTF_USESHOWWINDOW, 那么wShowWindow可以是除了SW_SHOWDEFAULT之外的任何其他值
    StartupInfo.wShowWindow=SW_HIDE;//将STARTUPINFO结构中bai的窗口属性设置为duSW_HIDE(隐藏)
    HANDLE hStdInput; //用于设定供控制台输入和输出用的缓存的句柄。按照默认设置,hStdInput用于标识键盘缓存,hStdOutput和hStdError用于标识控制台窗口的缓存
    HANDLE hStdOutput;
    HANDLE hStdError;

  • CreateProcess():用来创建一个新的进程和它的主线程,这个新进程运行指定的可执行文件
 BOOL CreateProcess
(
	LPCTSTR lpApplicationName,//指向一个NULL结尾的、用来指定可执行模块的字符串;当被设为NULL时,可执行模块的名字必须处于 lpCommandLine 参数最前面并由空格符与后面的字符分开。
	LPTSTR lpCommandLine,//指向一个以NULL结尾的字符串,该字符串指定要执行的命令行
	LPSECURITY_ATTRIBUTES lpProcessAttributes,
	LPSECURITY_ATTRIBUTES lpThreadAttributes,
	BOOL bInheritHandles,
	DWORD dwCreationFlags,
	LPVOID lpEnvironment,
	LPCTSTR lpCurrentDirectory,
	LPSTARTUPINFO lpStartupInfo,//指向一个用于决定新进程的主窗体如何显示的STARTUPINFO结构体
	LPPROCESS_INFORMATIONlpProcessInformation//指向一个用来接收新进程的识别信息的PROCESS_INFORMATION结构体
);

  • PeekNamedPipe():预览一个管道中的数据,或取得与管道中的数据有关的信息;返回值BooL,非零表示成功,零表示失败
 BOOL WINAPI PeekNamedPipe(
	__in HANDLE hNamedPipe, //管道句柄
	__out_opt LPVOID lpBuffer, //读取输出缓冲区,可选
	__in DWORD nBufferSize, //缓冲区大小
	__out_opt LPDWORD lpBytesRead, //接收从管道中读取数据的变量的指针,可选
	__out_opt LPDWORD lpTotalBytesAvail, //接收从管道读取的字节总数
	__out_opt LPDWORD lpBytesLeftThisMessage//保存这次读操作后仍然保留在消息中的字符数。只能为那些基于消息的命名管道设置
);

  • ReadFile():从文件指针指向的位置开始将数据读出到一个文件中, 且支持同步和异步操作
 BOOL ReadFile(
    HANDLE hFile,//文件的句柄
    LPVOID lpBuffer,//用于保存读入数据的一个缓冲区
    DWORD nNumberOfBytesToRead,//要读入的字节数
    LPDWORD lpNumberOfBytesRead,//指向实际读取字节数的指针
    LPOVERLAPPED lpOverlapped//如文件打开时指定了FILE_FLAG_OVERLAPPED,那么必须,用这个参数引用一个特殊的结构。
//该结构定义了一次异步读取操作。否则,应将这个参数设为NULL
);
  • PeekMessage():该函数为一个消息检查线程消息队列,并将该消息(如果存在)放于指定的结构。
BOOL PeekMessage(
	LPMSG IpMsg,//接收消息信息的MSG结构指针
	HWND hWnd,//其消息被检查的窗口句柄
	UINT wMSGfilterMin,//指定被检查的消息范围里的第一个消息
	UINT wMsgFilterMax,//指定被检查的消息范围里的最后一个消息
	UINT wRemoveMsg//确定消息如何被处理,当参数取PM_NOREMOVE时,PeekMessage处理后,消息不从队列里除掉
);
  • TCHAR:通过define定义的字符串宏

  • 窗口句柄:系统通过窗口句柄来在整个系统中唯一标识一个窗口,发送一个消息时必须指定一个窗口句柄表明该消息由那个窗口接收。而每个窗口都会有自己的窗口过程,所以用户的输入就会被正确的处理。例如有两个窗口共用一个窗口过程代码,你在窗口一上按下鼠标时消息就会通过窗口一的句柄被发送到窗口一而不是窗口二

使用管道实现重定向功能。在程序中启动新的进程,在新进程中执行ping.exe程序,使用管道技术将ping.exe的输出结果重定向到Windows的窗口中。在进程执行结束后,恢复系统默认的输入/输出。 代码如下:
#include<windows.h>
#define BUF_SIZE 1000
TCHAR PipeData[BUF_SIZE]="\0";
LRESULT CALLBACK myWndProc(HWND hWnd,UINT uMsgId,WPARAM wParam,LPARAM lParam);
void AppendText(HWND hwnd);
void OnPing(HWND hwnd);
void PeekAndPump();

int WINAPI WinMain(HINSTANCE hInst,HINSTANCE hPreInst,LPSTR pszCmdLine,int nCmdShow)
{
	static char szAppName[]="输出重定向!";
	WNDCLASS wndClass;
	HWND hWnd;
	MSG msg;
	wndClass.style=CS_VREDRAW|CS_HREDRAW;//CS_HREDRAW:一旦移动或尺寸调整使客户区的宽度发生变化,就重新绘窗口;CS_VREDRAW:一旦移动或尺寸调整使客户区的高度发生变化,就重新绘制窗口
	wndClass.lpfnWndProc=myWndProc;//类的默认窗口实现程序是myWndProc这个函数实现的
	wndClass.cbClsExtra=0;//窗口类无扩展
	wndClass.cbWndExtra=0;//窗口实例无扩展
	wndClass.hInstance=hInst;//定义应用程序实例句柄
	wndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION);//把第一个参数设置为NULL,表示使用系统预定义图标(也可以使用自定义的图标);将标题栏上的图标定义为IDI_APPLICATION
	wndClass.hCursor=LoadCursor(NULL,IDC_ARROW);//把第一个参数设置为NULL,表示使用系统预定义光标;IDC_ARROW 标准的箭头
	wndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH);//获得库存的白色画刷,赋给窗口背景色
	wndClass.lpszMenuName=NULL;//设置窗口类的菜单
	wndClass.lpszClassName=szAppName;//设置窗口类名称
	if(0==RegisterClass(&wndClass))//注册窗口类并判断注册是否成功
		return 0;
	hWnd=CreateWindow(
		szAppName,//窗口类结构体的字符串名称
		szAppName,//窗口的标题
		WS_OVERLAPPEDWINDOW,//窗口的风格
		CW_USEDEFAULT,//窗口左上角的x轴坐标
		CW_USEDEFAULT,//窗口左上角的y轴坐标
		CW_USEDEFAULT,//窗口的宽度
		CW_USEDEFAULT,//窗口的高度
		NULL,//本窗口的父窗口
		NULL,//本窗口的菜单
		hInst,//与当前窗口相连的实例句柄,
		NULL//CREATESTRUCT的附加参数
	);
	if(hWnd==0) return 0;
	ShowWindow(hWnd,nCmdShow);//显示窗口类
	UpdateWindow(hWnd);//更新消息
	while(GetMessage(&msg,NULL,0,0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

LRESULT CALLBACK myWndProc(HWND hWnd,UINT uMsgId,WPARAM wParam,LPARAM lParam)
{
	switch(uMsgId)
	{
	case WM_CREATE:
		return 0;
	case WM_PAINT:
		AppendText(hWnd);
		return 0;
	case WM_CHAR:
		if(wParam=='s'||wParam=='S')
			OnPing(hWnd);
	case WM_CLOSE:
		DestroyWindow(hWnd);
		return 0;
	case WM_DESTROY:
		PostQuitMessage(0);
		return 0;
	default:
		return DefWindowProc(hWnd,uMsgId,wParam,lParam);
	}
}

void AppendText(HWND hwnd)
{
	static TCHAR Data[10*BUF_SIZE]="\0";
	HDC hDC;
	PAINTSTRUCT paintStruct;
	RECT clientRect;
	strcat(Data,PipeData);
	PipeData[0]='\0';
	hDC=BeginPaint(hwnd,&paintStruct);
	if(hDC!=NULL)
	{
		TextOut(hDC,0,0,"Press s or S to start ping",25);
		GetClientRect(hwnd,&clientRect);
		DrawText(hDC,Data,-1,&clientRect,DT_LEFT|DT_VCENTER);
		EndPaint(hwnd,&paintStruct);
	}
}

void OnPing(HWND hwnd)
{
	LPCTSTR szCommand="ping.exe 127.0.0.1";//LPCTSTR就表示一个指向const对象的指针;指向以null结尾的命令字符串:"命令 设备参数"
	LPCTSTR szCurrentDirectory="c:\\windows\\system32\\";
	DWORD BytesLeftThisMessage=0;
	DWORD NumBytesRead;
	DWORD TotalBytesAvailable=0;
	HANDLE PipeReadHandle;//管道读句柄
	HANDLE PipeWriteHandle;//管道写句柄
	PROCESS_INFORMATION ProcessInfo;
	SECURITY_ATTRIBUTES SecurityAttributes;
	STARTUPINFO StartupInfo;
	BOOL Success;
	ZeroMemory(&StartupInfo,sizeof(StartupInfo));//用0来填充,即初始化结构体
	ZeroMemory(&ProcessInfo,sizeof(ProcessInfo));
	ZeroMemory(&SecurityAttributes,sizeof(SecurityAttributes));
	SecurityAttributes.nLength=sizeof(SECURITY_ATTRIBUTES);//设置安全属性结构体
	SecurityAttributes.bInheritHandle=TRUE;
	SecurityAttributes.lpSecurityDescriptor=NULL;
	Success=CreatePipe(&PipeReadHandle,&PipeWriteHandle,&SecurityAttributes,0);
	if(!Success) return;
	StartupInfo.cb=sizeof(STARTUPINFO);
	StartupInfo.dwFlags=STARTF_USESHOWWINDOW|STARTF_USESTDHANDLES;
	StartupInfo.wShowWindow=SW_HIDE;
	StartupInfo.hStdOutput=PipeWriteHandle;
	StartupInfo.hStdError=PipeWriteHandle;
	Success=CreateProcess(NULL,(LPTSTR)szCommand,NULL,NULL,TRUE,0,NULL,szCurrentDirectory,&StartupInfo,&ProcessInfo);
	if(!Success) return;
	for(;;)
	{
		NumBytesRead=0;
		Success=PeekNamedPipe(PipeReadHandle,PipeData,1,&NumBytesRead,&TotalBytesAvailable,&BytesLeftThisMessage);
		if(!Success) break;
		if(NumBytesRead)
		{
			Success=ReadFile(PipeReadHandle,PipeData,BUF_SIZE-1,&NumBytesRead,NULL);
			if(!Success) break;
			PipeData[NumBytesRead]='\0';
			InvalidateRect(hwnd,NULL,TRUE);
			PeekAndPump();
		}
		else
		{
			if(WaitForSingleObject(ProcessInfo.hProcess,0)==WAIT_OBJECT_0) break;
			PeekAndPump();
			Sleep(100);
		}
	}
	CloseHandle(ProcessInfo.hThread);
	CloseHandle(ProcessInfo.hProcess);
	CloseHandle(PipeReadHandle);
	CloseHandle(PipeWriteHandle);
}

void PeekAndPump()
{
	MSG Msg;
	while(PeekMessage(&Msg,NULL,0,0,PM_NOREMOVE))
		if(GetMessage(&Msg,NULL,0,0))
		{
			TranslateMessage(&Msg);
			DispatchMessage(&Msg);
		}
}

运行结果如下图:

在这里插入图片描述

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值