HelloWin详解
(注意:遇到程序在弄懂之后一定要自己去敲,一定要自己去敲,一定要自己去敲)
(注意:遇到程序在弄懂之后一定要自己去敲,一定要自己去敲,一定要自己去敲)
(注意:遇到程序在弄懂之后一定要自己去敲,一定要自己去敲,一定要自己去敲)
代码如下:
#include<windows.h>
#include<mmsystem.h>
#pragma comment(lib,"WINMM.LIB")
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PSTR szCmdLine, int iCmdShow)
{
static TCHAR szAppName[] = TEXT("HelloWin");
HWND hwnd;
MSG msg;
WNDCLASS wndclass; // 窗口类
wndclass.style = CS_HREDRAW | CS_VREDRAW; // 窗口类型的样式
wndclass.lpfnWndProc = WndProc; // 窗口处理函数
wndclass.cbClsExtra = 0; // 窗口扩展
wndclass.cbWndExtra = 0; // 窗口实例扩展
wndclass.hInstance = hInstance; // 实例句柄
wndclass.hIcon = LoadIcon(NULL, IDI_APPLICATION); // LoadIcon() 加载图标
wndclass.hCursor = LoadCursor(NULL, IDC_ARROW); // LoadCursor() 加载鼠标光标
wndclass.hbrBackground = (HBRUSH)GetStockObject(WHITE_BRUSH); // 窗口背景色 // GetStockObject() 获取一个图形对象
wndclass.lpszMenuName = NULL; // 窗口菜单
wndclass.lpszClassName = szAppName; // 窗口类名
if (!RegisterClass(&wndclass)) // RegisterClass() 为应用程序的窗口注册一个窗口类
{
MessageBox(NULL, TEXT("This program requires Windows NT!"), szAppName, MB_ICONERROR); // 显示消息框
return 0;
}
hwnd = CreateWindow(szAppName, TEXT("The Hello Program"), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT,
CW_USEDEFAULT, CW_U SEDEFAULT, NULL, NULL, hInstance, NULL); // CreateWindow() 基于窗口类创建一个窗口
ShowWindow(hwnd, iCmdShow); // 显示窗口
UpdateWindow(hwnd); // 指示窗口对其自身进行重绘
while (GetMessage(&msg, NULL, 0, 0)) // 从消息队列获取消息
{
TranslateMessage(&msg); // 翻译键盘消息
DispatchMessage(&msg); // 将消息发送给窗口过程 (即 WndProc()函数 )
}
system("pause");
return msg.wParam;
}
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC); // 播放声音文件
return 0;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps); // 标明窗口绘制开始
GetClientRect(hwnd, &rect); // 获取窗口客户区尺寸
DrawText(hdc, TEXT("Hello,Windows 98"), -1, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); // 显示一个文本字符串
EndPaint(hwnd, &ps); // 结束窗口绘制
return 0;
case WM_DESTROY:
PostQuitMessage(0); // 将 “退出” 消息插入消息队列
return 0;
}
return DefWindowProc(hwnd, message, wParam, lParam); // 返回 默认的消息处理的结果
}
看起来似乎第二行就懵逼吧!哈哈哈!
因为在程序中使用PlaySound函数时需要在 #include<windows.h> 后面加上 (不能在前面加 ):
#include <mmsystem.h>
#pragma comment(lib, "WINMM.LIB")
LRESULT是一个数据类型,指的是从 窗口程序 或者 回调函数返回的32位值。
在WINNT.H中typedef long LONG;
在WINDEF.H中typedef LONG LRESULT;
所以LRESULT就是长整型。之所以取名类LRESULT,是因为L即long;
result表示结果,说明这个函数的返回值是某个结果。
CALLBACK是由用户设计却由 Windows系统 呼叫的函数,统称为callback函数。
某些 API 函数要求以 callback 作为你参数之一。
实际上CALLBACK就是 __stdcall(回调函数),这里也叫窗口函数,来执行窗口的消息循环。
在建立窗口类的时候,可以指明窗口函数地址。
WndProc是函数名,后面跟着的自然就是参数类型了。这和 C 语言一样,函数的声明可以省略参数名。
(HWND, UINT, WPARAM, LPARAM)各个参数类型的意思:
前面谈到过:
HWND 是窗口的句柄。hwnd用来接收当前消息的窗口句柄。
UINT 也谈过,就是unsigned int 无符号整型,两个字节。Messsage是被传过来的消息。
WPARAM WP是前缀名,表示宽字符指针。wParam 是用来附加在消息上的数据。和 MSG 结构体一样
LPARAM LP是前缀名,表示长指针。lParam 也是用来附加在消息上的数据。和 MSG 结构体一样。
接下来我们看到函数的定义部分:
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
HDC hdc;
PAINTSTRUCT ps;
RECT rect;
switch (message)
{
case WM_CREATE:
PlaySound(TEXT("hellowin.wav"), NULL, SND_FILENAME | SND_ASYNC); // 播放声音文件
return ;
case WM_PAINT:
hdc = BeginPaint(hwnd, &ps); // 标明窗口绘制开始
GetClientRect(hwnd, &rect); // 获取窗口客户区尺寸
DrawText(hdc, TEXT("Hello,Windows 98"), -, &rect, DT_SINGLELINE | DT_CENTER | DT_VCENTER); // 显示一个文本字符串
EndPaint(hwnd, &ps); // 结束窗口绘制
return ;
case WM_DESTROY:
PostQuitMessage(); // 将 “退出” 消息插入消息队列
return ;
}
return DefWindowProc(hwnd, message, wParam, lParam); // 返回 默认的消息处理的结果
}
或许看到 HDC,PAINTSTRUCT,RECT 你又懵逼了!
HDC:设备上下文 句柄(可以理解为指向DC结构的指针),它指向一块描述设备的相关的内容的内存块。
DC: 设备上下文(设备描述表),是WINDOWS的一种数据类型。
这样说的话,又出现了新的问题,设备上下文 又是啥?
HDC设备上下文 是一种包含有关某个设备(如显示器或打印机)的绘制属性信息的 Windows 数据结构。
所有绘制调用都通过设备上下文对象进行,这些对象封装了用于绘制线条、形状和文本的 Windows API。
那 PAINTSTRUCT 是什么呢?
从名字上不难看出,应该是和绘图有关的结构体。 ps自然就是结构体变量。
typedef struct tagPAINTSTRUCT { HDC hdc; BOOL fErase; RECT rcPaint; BOOL fRestore; BOOL fIncUpdate; BYTE rgbReserved[]; } PAINTSTRUCT, *PPAINTSTRUCT;
PAINTSTRUCT 包含了用于绘制窗口客户区的信息。例如要更新的客户区的矩形区域的大小等等。
程序处理 WM_PAINT 消息时将会用到它,该结构体的作用就是重绘客户区的。
每个窗口要有一个PAINTSTRUCT结构来记录一些绘制信息,PAINTSTRUCT结构保存了窗口绘制客户区的一些信息。
例如,绘制客户区时是否要清除背景色,要更新的客户区的矩形区域的大小等等。
那RECT又是什么东西呢?
其实它也是一个结构体,用来显示窗口的位置的。可以看下它的结构体:
typedef struct _RECT { LONG left; LONG top; LONG right; LONG bottom; } RECT, *PRECT;
这个结构体的意思很明确了,就不强加解释了。
接下来我们看到 switch(message) 语句:
看到这段代码,可以知道程序是想针对传入的消息进行处理。
消息的值可能是:WM_CREATE, WM_PAINT, WM_DESTROY 等。
你们肯定想到了,case 后面的都应该是整型常量表达式。
其实消息的值都是这类型,只不过内部把这些数值用宏定义 #define 处理了。
现在来看第一个 case 语句:WM_CREATE 是窗口消息中的 请求创建窗口时的消息
PlaySound() 原型:
BOOL PlaySound(LPCSTR pszSound, HMODULE hmod,DWORD fdwSound);
PlaySound参数:pszSound 是指定了要播放声音的字符串,该参数可以是WAVE文件的名字,
或是WAV资源的名字,或是内存中声音数据的指针,或是在系统注册表WIN.INI中定义的系统事件声音。
如果该参数为NULL则停止正在播放的声音。
参数:hmod 是应用程序的实例句柄,除非pszSound的指向一个资源标识符
(即fdwSound被定义为SND_RESOURCE),否则必须设置为NULL。
参数:fdwSound是标志的组合。若成功则函数返回TRUE,否则返回FALSE。
返回值:非零表示成功,零表示失败。
第二个case 语句:WM_PAINT 是窗口消息中的 客户区重绘消息
这个消息在Windows程序设计中是很重要的。
当窗口显示区域的一部分显示内容或者全部变为“无效”,以致于必须“更新画面”时,将由这个消息通知程序。
当需要绘制一部分应用窗口的时候,这个消息被Windows或者其他应用程序绘制调用。
对WM_PAINT的处理几乎总是从一个 BeginPaint() 调用开始:hdc = BeginPaint (hwnd, &ps) ;
而以一个 EndPaint() 调用结束:EndPaint (hwnd, &ps) ;
在这两个调用中,第一个参数都是程序的窗口句柄,第二个参数是指向型态为PAINTSTRUCT的结构指针。
PAINTSTRUCT结构中包含一些窗口消息处理程序,可以用来更新显示区域的内容。
BeginPaint调用令整个显示区域有效,并传回一个“设备上下文句柄”。在窗口的显示区域显示文字和图形需要设备上下文句柄。
但是从BeginPaint传回的设备上下文句柄不能在显示区域之外绘图。EndPaint释放设备上下文句柄,使之不再有效。
调用完BeginPaint之后,WndProc接着调用GetClientRect:
GetClientRect (hwnd, &rect) ;
第一个参数是程序窗口的句柄。第二个参数是一个指针,指向一个RECT型态的rectangle结构。
该结构有四个LONG字段,分别为left、top、right和bottom。GetClientRect将这四个字段设定为窗口显示区域的尺寸。
left和top字段通常设定为0,right和bottom字段设定为显示区域的宽度和高度(像素点数)。
WndProc除了将该RECT结构指针作为DrawText的第四个参数传递外,不再对它做其它处理。
DrawText() 原型:
int DrawText(HDC hdc, LPCTSTR lpString, int nCount, LPRECT lpRect, UINT uFormat);
参数:
hdc:设备环境句柄。
lpString:指向将被写入的字符串的指针,如果参数nCount是-1,则字符串必须是以\0结束的。
如果uFormat包含DT_MODIFYSTRING,则函数可为此字符串增加4个字符,存放字符串的缓冲区必须足够大,能容纳附加的字符。
nCount:指向字符串中的字符数。如果nCount为-1,则lpString指向的字符串被认为是以\0结束的,
DrawText() 会自动计算字符数。
lpRect:指向结构RECT的指针,其中包含正文将被置于其中的矩形的信息(按逻辑坐标)。
uFormat:指定格式化正文的方法。该值都是 DT_*** 的样式。
返回值:如果函数调用成功,返回值是正文的高度;如果函数调用失败,返回值是0。
第三个 case 语句: WM_DESTROY 是窗口消息中的 窗口摧毁消息
PostQuitMessage() 该函数向系统表明有个线程有终止请求。通常用来响应 WM_DESTROY 消息。
函数原型:VOID PostQuitMessage(int nExitCode);
参数 nExitCode:指定应用程序退出代码。此值被用作消息 WM_QUIT 的 wParam 参数。
PostQuitMessage寄送一个WM_QUIT消息给线程的消息队列并立即返回;此函数向系统表明有个线程请求在随后的某一时间终止。
当线程从消息队列里取得WM_QUIT消息时,应当退出消息循环并将控制返回给系统。返回给系统的退出值必须是消息WM_QUIT的。
返回值:无
DefWindowProc() 调用缺省的窗口过程来为应用程序没有处理的任何窗口消息提供缺省的处理。
函数功能:该调用DefWindowProc函数时使用窗口过程接收的相同参数。
DefWindowProc这个函数是默认的窗口处理函数,我们可以把不关心的消息都丢给它来处理。
这个函数在处理关闭窗口消息WM_CLOSE时,是调用 DestroyWindow() 关闭窗口并且发 WM_DESTROY 消息给应用程序;
而它对WM_DESTROY这个消息是不处理的(考虑为什么?);我们在应用程序中对这个消息的处理是发出WM_QUIT消息。
因此WM_CLOSE、WM_DESTROY、WM_QUIT这三个消息是先后产生的。
或许你早已运行该程序了,发现没有声音!其实是声音文件的位置错了。
你可以把TEXT("hellowin.wav") 改成 TEXT("C:\\Windows\\media\\Ring03.wav")
因为在 C:\\Windows\\media 这个目录下有许多的 wav 型的文件。这样就会有声音了。
HWND 的数据类型表示 句柄:
一个句柄是指使用的一个唯一的整数值,即一个4字节(64位程序中为8字节)长的数值。
用来标识应用程序中的不同对象和同类中的不同的实例,诸如,一个窗口,按钮,图标,滚动条,输出设备,控件或者文件等。
应用程序能够通过句柄访问相应的对象的信息,但是句柄不是指针,程序不能利用句柄来直接阅读文件中的信息。
如果句柄不再 I/O 文件中,它是毫无用处的。
句柄是Windows用来标志应用程序中建立的或是使用的唯一整数,Windows大量使用了句柄来标识对象。
句柄与普通指针的区别在于,指针包含的是引用对象的内存地址,而句柄则是由系统所管理的引用标识。
该标识可以被系统重新定位到一个内存地址上。这种间接访问对象的模式增强了系统对引用对象的控制。
WINDOWS程序中并不是用物理地址来标识一个内存块,文件,任务或动态装入模块的。
相反,WINDOWS API给这些项目分配确定的句柄,并将句柄返回给应用程序,然后通过句柄来进行操作。
一个句柄,只有当唯一地确定了一个项目的时候,它才开始有意义。
句柄对应着项目表中的一项,而只有WINDOWS本身才能直接存取这个表,应用程序只能通过API函数来处理不同的句柄。
总而言之:句柄就是对窗口及窗口的控件进行标识的,如果要改变窗口或它的控件的话,就需要指定窗口或控件对它进行改变。
MSG是 Windows 程序中的结构体。在Windows程序中,消息是由MSG结构体来表示的。
typedef struct tagMSG { HWND hwnd; UINT message; WPARAM wParam; LPARAM lParam; DWORD time; POINT pt; } MSG,*PMSG;
成员变量含义:
WNDCLASS是一个由系统支持的结构,用来储存某一类窗口的信息,如ClassStyle,消息处理函数,Icon,Cursor,背景Brush等。
也就是说,CreateWindow只是将某个WNDCLASS定义的窗体变成实例。
结构体 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;
RegisterClass() :
RegisterClass注册后再调用 CreatWindow() 中使用的窗口类。
该函数的声明: ATOM RegisterClass(__inCONST WNDCLASS *lpWndClass);
lpWndClass:指向一个 WNDCLASS 结构的指针。在将它传递给函数之前,必须在该结构中填充适当的类属性。
ATOM类型为Windows中定义的新数据类型,其即unsigned short类型。
如果函数成功,返回值是唯一标识已注册的类的一个原子;如果函数失败,返回值为0。
CreatWindow() :
HWND CreateWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName, DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hWndParent, HMENU hMenu, HANDLE hlnstance, LPVOID lpParam
);
主要参数:
lpClassName
备注:
在返回前,CreateWindow给窗口过程发送一个WM_CREATE消息。
对于层叠,弹出式和子窗口,CreateWindow给窗口发送WM_CREATE,WM_GETMINMAXINFO和WM_NCCREATE消息。
消息WM_CREATE的IParam参数包含一个指向CREATESTRUCT结构的指针。
如果指定了WS_VISIBLE风格,CreateWindow向窗口发送所有需要激活和显示窗口的消息。
ShowWindow() :
函数功能:该函数设置指定窗口的显示状态。
函数原型:BOOL ShowWindow(HWND hWnd, int nCmdShow);
参数:
hWnd:指窗口句柄。
nCmdShow:指定窗口如何显示。
如果发送应用程序的程序提供了STARTUPINFO结构,则应用程序第一次调用ShowWindow时该参数被忽略。
备注:
应用程序第一次调用ShowWindow时,应该使用WinMain函数的nCmdshow参数作为它的nCmdShow参数。
在随后调用ShowWindow函数时,必须使用列表中的一个给定值,而不是由WinMain函数的nCmdSHow参数指定的值。
UpdateWindow() :
如果窗口更新的区域不为空,UpdateWindow函数就发送一个 WM_PAINT 消息来更新指定窗口的客户区。
函数绕过应用程序的消息队列,直接发送WM_PAINT消息给指定窗口的窗口过程,如果更新区域为空,则不发送消息。
函数原型:
);
返回值:如果函数调用成功,返回值为非零值。如果函数调用不成功,返回值为零。
函数声明:
GetMessage(LPMSG lpMsg,HWND hWnd,UINT wMsgFilterMin,UINT wMsgFilterMax)
备注:
函数原型:
函数功能:该函数分发一个消息给窗口程序。通常消息从GetMessage函数获得或者TranslateMessage函数传递的。
消息被分发到回调函数(过程函数),作用是消息传递给操作系统,
然后操作系统去调用我们的回调函数,也就是说我们在窗体的过程函数中处理消息。
函数原型:LONG DispatchMessage (CONST MSG*lpmsg);
前缀 | 常量 |
CS | 类风格选项 |
CW | 创建窗口选项 |
DT | 文本绘制选项 |
IDI | 图标的 ID 号 |
IDC | 光标的 ID 号 |
MB | 消息框选项 |
SND | 声音选项 |
WM | 窗口消息 |
WS | 窗口风格 |
数据结构:
结构 | 含义 |
MSG | 消息结构 |
WNDCLASS | 窗口类结构 |
PAINTSTRUCT | 绘制结构 |
RECT | 矩形结构 |
句柄标识符:
标识符 | 含义 |
HINSTANCE | 实例句柄---程序本身 |
HWND | 窗口句柄 |
HDC |
设备环境句柄 |
HelloWin详解的更多相关文章
- Linq之旅:Linq入门详解(Linq to Objects)
示例代码下载:Linq之旅:Linq入门详解(Linq to Objects) 本博文详细介绍 .NET 3.5 中引入的重要功能:Language Integrated Query(LINQ,语言集 ...
- 架构设计:远程调用服务架构设计及zookeeper技术详解(下篇)
一.下篇开头的废话 终于开写下篇了,这也是我写远程调用框架的第三篇文章,前两篇都被博客园作为[编辑推荐]的文章,很兴奋哦,嘿嘿~~~~,本人是个很臭美的人,一定得要截图为证: 今天是2014年的第一天 ...
- EntityFramework Core 1.1 Add、Attach、Update、Remove方法如何高效使用详解
前言 我比较喜欢安静,大概和我喜欢研究和琢磨技术原因相关吧,刚好到了元旦节,这几天可以好好学习下EF Core,同时在项目当中用到EF Core,借此机会给予比较深入的理解,这里我们只讲解和EF 6. ...
- Java 字符串格式化详解
Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...
- Android Notification 详解(一)——基本操作
Android Notification 详解(一)--基本操作 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 源码:AndroidDemo/Notification 文中如有纰 ...
- Android Notification 详解——基本操作
Android Notification 详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 前几天项目中有用到 Android 通知相关的内容,索性把 Android Notificatio ...
- Git初探--笔记整理和Git命令详解
几个重要的概念 首先先明确几个概念: WorkPlace : 工作区 Index: 暂存区 Repository: 本地仓库/版本库 Remote: 远程仓库 当在Remote(如Github)上面c ...
- Drawable实战解析:Android XML shape 标签使用详解(apk瘦身,减少内存好帮手)
Android XML shape 标签使用详解 一个android开发者肯定懂得使用 xml 定义一个 Drawable,比如定义一个 rect 或者 circle 作为一个 View 的背景. ...
- Node.js npm 详解
一.npm简介 安装npm请阅读我之前的文章Hello Node中npm安装那一部分,不过只介绍了linux平台,如果是其它平台,有前辈写了更加详细的介绍. npm的全称:Node Package M ...
随机推荐
- linux安装python串口工具pyserial遇到不能成功导入的问题
常规方法:pip install pyserial导入serial后提示: 解决方法:apt install python3-serial 参考:https://stackoverflow.com/q ...
- java对象与java对象引用的区别
java对象与java对象引用的区别 对象与对象引用的区别 直接用例子说话吧 Person per = new Person("张三"); 这一条语句,其实包括了四个动作: 右边的 ...
- 正确应用Java数组
一.数组的特点 数组与其他容器的区别有三方面:效率.类型和保存基本类型的能力. 1.效率.数组是一种效率最高的存储和随机访问对象引用序列的方式.数组是一段连续地址空间内的线性序列,所以访问非常快.但也 ...
- linux 操作系统级别监控 df 命令
df命令可以查看当前系统磁盘空间的使用情况 命令:df -h du -sh * 查看目录文件暂用磁盘大小 如果磁盘空间不够,需清理磁盘 磁盘速度测试,如果磁盘性能不好,性能测试数据会不准确(读写速度) ...
- python接口自动化测试二十七:密码MD5加密 ''' MD5加密 ''' # 由于MD5模块在python3中被移除 # 在python3中使用hashlib模块进行md5操作 import hashlib # 待加密信息 str = 'asdas89799,.//plrmf' # 创建md5对象 hl = hashlib.md5() # Tips # 此处必须声明encode # 若写法为
python接口自动化测试二十七:密码MD5加密 ''' MD5加密 '''# 由于MD5模块在python3中被移除# 在python3中使用hashlib模块进行md5操作import has ...
- 06: RGB、YUV和HSV颜色空间模型
RGBA是代表Red(红色)Green(绿色)Blue(蓝色)和Alpha的色彩空间 YUV:Y"表示明亮度(Luminance或Luma),也就是灰阶值:而"U"和&q ...
- 新手学习FFmpeg - 调用API完成两个视频的任意合并
本次尝试在视频A中的任意位置插入视频B. 在上一篇中,我们通过调整PTS可以实现视频的加减速.这只是对同一个视频的调转,本次我们尝试对多个视频进行合并处理. Concat如何运行 ffmpeg提供了一 ...
- Netty源码分析 (十一)----- 拆包器之LengthFieldBasedFrameDecoder
本篇文章主要是介绍使用LengthFieldBasedFrameDecoder解码器自定义协议.通常,协议的格式如下: LengthFieldBasedFrameDecoder是netty解决拆包粘包 ...
- 可能是 Python 中最火的第三方开源测试框架 pytest
作者:HelloGitHub-Prodesire HelloGitHub 的<讲解开源项目>系列,项目地址:https://github.com/HelloGitHub-Team/Arti ...
- String的优化 Stringbuffer和Stringbuilder
string 上次说到string是最好衍生出来的一种字符类型,实现原理是由char[].我们知道数组一旦创建时不可更改的,所以每一次进行字符串的拼接都是在new一个新的字符串进行添加,这样的话对内存 ...