百科问答小站 logo
百科问答小站 font logo



如何用 C++ 从零编写 GUI? 第1页

  

user avatar   ypluo 网友的相关建议: 
      

楼主你其实是在找这本书:《精通嵌入式Linux编程:构建自己的GUI环境


user avatar   balloonwj 网友的相关建议: 
      

谢邀。看了很多答案,都是教新手如何利用各种图形库,我个人觉得这是一种误导。所有不推荐新手学习原理,而上来就是各种库的,是走不远的。目前大多数使用的有界面的程序还是Windows系统上的各种软件,所以我们就说说Windows上如何利用C/C++写出有界面的程序。

下面是我的答案,个人成长经历,不喜勿喷。

写在前面的话

很多年以前,我正如现在那些初学C或者C++的同学,一直心存这样的迷惑:人们都说现在我在电脑上看到了的大多数软件,比如QQ、迅雷、千千静听等等,都是用C/C++编写出来的,可是我现在都把C或者C++整本教材都学完了,我还是整天只能面对那个黑洞洞的命令行窗口,就算把书翻到最后一页也找不到如何用C/C++像QQ这样有界面的程序,这是怎么回事?
的确,这样的迷惑,困扰大多数的开始学C和C++的学生,他们这样的迷惑也很难有人给予解答。因为,大学里面教C/C++的老师们往往也是理论脱离实际,没有多少实际经验,只能照本宣科了。
于是很多同学因为学习的苦闷和难以获得喜悦或者成就感开始放弃C/C++的学习,开始转向其他的方面,比如Web设计,通俗地说也就是网页制作,他们惊喜地发现用一个个简单的<div>或者alert()函数就可以作出一个有界面的框框或者显示一个对话框,这比C/C++要容易的多,而且成果也明显;又有一些同学发现了C#或者Java这样的编程语言,他们只要在编程工具中将一个个各式各样的按钮拖进一个被称为“窗体”的对象中,然后运行程序就能生成一个个有界面的程序,而且这些编程语言学起来容易。
这个时候C/C++赫然已经被同学们抛弃了。


根据我个人经历和经验,个人始终认为C/C++是世界上最实用的编程语言,因为我是一个由原先的Web设计者兼Flash程序开发者转变成今天的一个C/C++程序员。我之所以放弃Web设计和Flash编程,是在我遇到了一个叫Charles Petzold写的一本叫《windows程序设计》书以后,以前写的那些东西,虽然好玩,但是我始终都耿耿于怀,因为,那些你所能制作出来的东西,只是别人不感兴趣罢了,只要别人有天对它们有了兴趣,同样能学会,而且这些东西必须借助一定的运行环境,而不是像QQ这样的软件直接运行于操作系统之上。我们来看几个我大学时代写的几个“软件”,注意里的“软件”一词我加了引号:






上面的图片是我为我大学毕业时花了四个月时间为我们班做的毕业纪念册,它是一个可直接运行于桌面上的程序,但是除了制作比较精美一点,实际并没有多少技术含量,Flash编程是我的强项,我先用flash做成动画形式,也就是一个个swf文件,然后用flash打包工具生成exe程序,这样它就能运行于桌面了。我虽然实现了这个程序的核心部分,但是用的那个打包工具程序如何编写的,工作原理是什么,我那个时候一无所知。这个程序的网络版,可以访问如下网址:hootina.org/jnc/byjnc.h


我们再来看看我曾经写过的一个三维立体的音乐播放器,如下图:



这个播放器软件是一个能在桌面上根据鼠标位置自动旋转的软件,并且可以拖动位置,也能通过右键菜单控制歌曲。但,现在看来同样没有什么技术含量。这是2011年五月在上海九维上班有天晚上在公司花了一宿时间做的,核心程序是一个flash,flash可以在flash播放器中转动,而且可以从网络加载歌曲,做好后通过一个flash打包工具让它能直接运行在桌面上。当时写这个程序是为了练习自己的面向对象编程技能。它通过一个配置文件从网络或者本地加载歌曲,配置文件是个xml格式的文本文件:


如果你需要这个软件,可从这个网址下载:hootina.org/res-for-dow


再来看一个C#版的程序:



我来介绍下这个程序吧,这个程序是许多年以前武汉大学一个同学发给我帮着修改的,程序的目的是为了演示一个森林火灾预报。启动程序以后,先点击Random按钮随机生成一片绿地,然后鼠标在绿地某个地方单击,表示从这里产生火源,然后点击start按钮火势开始向四周蔓延。
这个同学的程序存在一个问题,大家看第二个图,当点击start让火势蔓延,结果整个原来随机覆盖的绿地变成百分百覆盖了。我当时花了一下午时间帮她改好。由于隐私问题,改过后的程序我就不截图了。
这个程序是用C#写的,想到当时何军军同学因为不知道需要一个.net框架环境才能运行而抓耳挠腮的样子,我就觉得好笑。你看用C#写出来的程序还是不能直接运行在系统上。哈哈~
正是这些借助他物的原因,我一直对这些徒有其表的花哨技术心存芥蒂。因为我想去了解下计算机底层,去夯实一些基础知识,以期将来有番作为。好在,我在中国地大读研的这两个年头里,我的老师没有给我安排过多的任务,加上史超同学的照顾,让我有时间和经历去学习去研究这些底层的东西。而Windows下的C/C++编程就是我的那把合适的钥匙。

现在我来回答文章开头提到的同学的困惑,为什么用学习了C/C++那么久还是无法写出有界面的软件。讲授C/C++的书,一般只会去讲解C/C++语法层次上的东西,而不会去介绍相关的平台的API函数。如果我们要用C/C++去写出有界面的程序,还得调用所在平台提供的接口函数,而这个平台我们现在大多数人见到的正是Windows操作系统,所以这些接口函数也就是Windows API了。说的更通用点,个人认为,编程语言和语言之间的差别多数是语法层次上的差别,一种编程语言本身不能做任何软件,你必须借助或者调用所在平台或者运行环境的API函数。拿C#来说,你之所以能作出一些界面或者窗口出来,不是C#本身就有这个功能,而是.net环境提供了这些东西,具体点就是CLR(C#运行时,或C#运行环境)。
我们再深入一点,你调用CLR提供的API接口,最后在底层CLR还是去调用操作系统的API函数,既然如此,我为什么隔一层CLR,不直接去调用系统API了,就这一点来说,这就是在操作系统之上用C/C++编程最大的优势,正如Charles Petzold所说的,我这里把书中原话摘录如下:
“显而易见,究竟用哪种方式编写Windows应用程序最好,其实并无一定之规。应用程序本身的特性应该是决定采用何种编程工具的最主要因素,但是无论将来你采用什么样的编程工具,通过了解Windows API从而深入理解Windows的工作原理,这本身就有很重要的意义。Windows是一个非常复杂的系统,在API之上加一层编程语言并不能消除其复杂性,最多不过是把复杂性隐藏起来而已。说不定什么时候,Windows复杂的那一面迟早会蹦出来拖你的后腿,懂得API能让你到时候更快地挣脱困境。

在基本Windows API之上的任何软件层或多或少都会限制你使用Windows的全部功能。比如,你或许发现采用Visual Basic来编写你的应用程序非常理想,但是就有那么一两项非常基本的功能Visual Basic无法支持。往往这个时候你得非要调用基本API。作为Windows程序员,我们的活动空间完全由API来规范,再没有什么其他方式比直接调用API更有效、更灵活多样了。
这段文字选自Charles Petzold的《Windows程序设计》第一章第十页。
我上面展示了很多图片,也是为了印证这个道理。C# 高级用户肯定写过这样的代码:



有时候Windows提供的一些功能C#没有,这个时候我们只能通过这种用中括号括起来的元标签的方法调用系统dll中的一些函数来为自己服务。这也用侧面说明了,操作系统API上层的任何软件层都没有系统原生API来的高效与直接。

但是如今的世界,懂这个道理的人甚多,最后他们还是离C/C++远去了。为什么呢?
就其主要原因,还是从学生时代来说吧,Windows编程实在太难了,不仅难而且成效不明显,你写了一堆代码恐怕都很难显示出一个按钮,相比较其他语言所写即所得,能坚持下来而不跟风的同学实在太少了。等到工作的时候,若不是从事这个,也很难有心境和时间去学了。
我们还是来举个例子,我现在分别用ActionScript和C语言为一个程序制作一个右键菜单(老外叫上下文菜单,Context Menu) 。
下面是ActionScript代码:


仅仅 就两行就自定义了一个右键菜单项,而且代码也很容易看懂。

现在,我们再来看看,如何用C语言实现同样的功能:


这段代码来自我三月份帮所里一同学写的一个批量生成Excel表格的项目。效果如下:


当我告诉你上面那段程序确实是C程序你可能不信,怎么会是C程序呢,上面出现的那些诸如HMENU这样的数据类型,C语言中可没有啊。Windows系统最初诞生的时候,C++语言还不存在,所以整个Windows系统都是用C加上少许的汇编写出来了。现在,就请跟随我,我将用C语言来一行行教你如何写出一个Windows程序出来,注意这里说的Windows程序是那种有窗口有按钮有菜单的界面程序,而不是你之前写的那些黑洞洞的控制台程序。当然,这两种程序本质上是一样的,请你记住这一点。同时,我们从教科书或者老师那里学到的C/C++知识并不完整,工作以后接触Windows编程,我也发现了这一点,很多很常用的C/C++语法,我们在教科书上从来就没学到,所以,我也将根据经验教你一些你所不知道的C/C++小技巧。



如何用C/C+编写窗口程序(非命令行程序)

这是我大学时代的一个困惑,今天我将原原本本详详细细地给予解答。我们在Windows平台下,要想写出有界面的程序,必须调用Windows提供的API函数


你所不知道的C语言细节1  
C语言中有一个关键字叫typedef,这个关键字是用来利用已有类型来定义新类型用的。这点多数C教材中都没有讲到过它,很多同学很容易把它与#define关键字搞混淆,下面我们来做个对比:


程序含义很简单,我们分别用define和typedef这两个关键字定义了两个新类型I1和I2,而测试结果发现,用typedef定义的新类型的确生效了,当然这种新类型本质还是int*(int型指针),而define关键却没有工作的那么好。
我们来分析下原因,define一般用来定义宏的,而这个宏在预编译(编译的前一个步骤)时被完全替换成其定义的东西,也就是纯粹的文本替换,而typedef却不是这样。也就是上述代码相当于:


总结起来,typedef关键字的确是新类型的定义方法,一旦用它定义了一个类型,到处可用,而不是什么简单的替换。为什么我要说到typedef这个关键字呢?第一,我当年在教材中也没学到这个关键字,所以开始接触Windows编程时甚是迷糊,总以为它和define替换类似,第二,整个windows系统中的数据类型就是通过typedef和define这两个关键字一步步地设计出来的。


你所不知道的C语言细节2
关于C语言的宏与宏定义大家一般对define关键字很熟悉,但是所谓的熟悉也只是知道些简单的应用,其实define定义宏可以写的很复杂。下面列举几个很常用但是却在教科书中基本不提的用法:


函数宏


看到了没,这种宏调用起来类似函数调用,所以叫做“函数宏”。这个知识点,我相信很多同学也知道这个。好下面,我们继续深入一步,看下面代码:


我的问题是表达式t(1)是什么?答案是L1。表达式t("Hello")的结果为L"Hello"。"##"在C语言中被称为合并操作符或者叫“令牌粘贴”,言下之意就是将##两边的东西连接成在一起,L和1连接在一起便成了L1,L与字符串"Hello"在一起便成了L"Hello"(注意:包含引号,原封不动地连接!)。 这是一个有用的C语言知识,请大家记住。


宏其实可以写的很复杂,甚至一行写不下可以写成多行,mfc中的消息宏就是一个例子:


注意由于这三行代码并没有以分号结尾,所以它们是三个宏,我们现在来看看这个三个宏如何定义的,试着看看哦,看看你能否看懂:




这三个宏就比较复杂了,我相信你在C或者C++教材中从来没见过这么复杂的宏定义吧,甚至这些宏定义一行写不下写成了多行,那些反斜杠就是续行符号。对于这么复杂的宏定义也请不要害怕,因为宏的本质还是原封不动地替换。我们一步步地替换下就看出来了,替换之后是这样的:



其中PTM_WARNING_DISABLE和PTM_WARNING_RESTORE又是定义的两个宏,__pragma是C/C++中用来设置编译器参数的指令,如果我不说,你在教科书上恐怕也学不到这个指令吧,这个指令很有用,你可以去搜索它,好好学习下。这两个宏展开之后,是用来不让编译器显示4867号警告。等这段代码执行完以后,取消这个不显示这个警告的设置。


这两个宏对理解上面的代码无帮助。我们来分析下上面展开的的代码,上面无法是一个叫做CMFCControlsApp这个C++类的两个成员函数GetMessageMap()GetThisMessageMap()的具体实现代码,两者返回类型是AFX_M指针类型。AFX_MSGMAP是什么类型?我们再看看:


哈,AFX_MSGMAP原来这是一个自定义的结构的类型。你可能觉得不尽兴,因为这个结构体里面定义的两个东西还不是你熟悉的C语言中的基本类型。好,那让我们继续展开,结构体第一个类型是一个返回值为AFX_MSGMAP常指针类型(const AFX_MSGMAP*)。你可能也不熟悉什么是函数指针,那么我们插播个广告来讲解下什么是函数指针以及如何定义一个函数指针。


函数指针
我们以C语言函数库中的signal()函数来说明吧,signal()函数的签名如下:



首先这个函数名为signal,其次它返回void*类型,这里对这个类型说明下,我们熟悉void类型,一般表示无返回类型,而void*类型 是一个指向内存某个区域的指针,这个块内存大小根据具体情况而定,比如可以将这个不确定的类型指针转换成合适的类型,如int*,float*,struct*等等,它不再含有空的意思,而只是那块内存数据类型不确定而已,但是在合适的时候还是会转换成实际的数据指针类型的。
然后是这个函数含有两个参数,第一个参数sig是一个int型,第二个参数就是一个函数指针类型,通俗地说就是调用这个signal函数时第一个参数必须传递一个int型数据,第二个参数必须传入一个函数名,C教材上告诉我们函数名本质上就是函数在内存中的入口地址。那么是不是什么样的函数都行呢?当然不是,必须是一个返回值为void类型(注意不是void*)含有一个参数为int类型的函数。


这里的的void (*func)(int)就是声明一个函数指针,你可以把*func看作是一个整体的函数名,那么func就是一个函数指针了。
这里的第二个形参名就是func,只是写的那么复杂,只是为了说明func是一个返回值为void含有一个int型参数的函数指针类型的参数。有点绕口,希望你能转变观念。还有一种用typedef定义函数指针的方法:


如上图, 可以将f1的地址(&f1)赋给pfn,而不能将&f2,&f3赋给pfn,f2是由于函数参数类型不符合,f3是由于函数返回类型不符合。
函数指针在Windows程序中广泛使用着,尤其是一些回调函数(后面会讲到)。

好了,广告完毕,回到AFX_MSGMAP结构上来:


这个结构体第一个参数类型是一个函数指针,这个函数返回值类型为AFX_MSGMAP*,函数无参数,其中PASCAL为函数的调用方式,函数的调用方式有很多种,不同的调用方式主要区别在于调用函数时函数的参数传递顺序(是由左向右还是由右向左),函数的堆栈由调用方还是被调用方来清理。常见的函数调用方式有__cdecl 、__stdcall 、fastcall等调用方式,我们写C/C++控制台程序时,函数调用方式为__cdecl方式,而windows API函数的调用方式为__stdcall方式,请记住这一点。到底调用方式具体是怎么回事,下文中详细道来。因为我们默认的调用方式是__cdecl,所以,我们自己编写函数的时候,我们总是将这个函数调用方式修饰方式省略。下面两种写法等价:


上面的PASCAL也是一个宏,我们来展开看看:


看到了没,它本质上的调用方式也是__stdcall


回到AFX_MSGMAP结构上来:


这个结构第一个参数名为pfnGetBaseMap,第二参数名为 lpEntries,这是一个AFX_MSGMAP_ENTRY指针类型(AFX_MSGMAP_ENTRY*),我们继续展开AFX_MSGMAP_ENTRY看看:


其中UINT类型和UINT_PTR两种类型定义如下:



可以发现这两种类型本质上就是C中的unsigned int类型。AFX_MSGMAP_ENTRY结构最后一个字段类型是AFX_PMSG,其定义如下:


其中AFX_MSG_CALL仍然是函数调用方式CCmdTarget是微软mfc提供的一个类,可见AFX_PMSG是一个函数指针类型,这种函数指针是一个类成员函数指针,有点特殊哦,其返回类型为void,参数也是void。

至此,我们发现即使再复杂的Windows程序也是通过C/C++语法和基础数据类型一步步组装起来的。所以只要认真分析,没什么好怕的。说了这么多,这是我想给大家传达的意思。

下面,我们通过一个简单但完整Windows UI的程序来告诉大家怎样利用现有的C/C++知识去阅读Windows程序。
按下列步骤在VS中建立一个Windows GUI程序(GUI的意思就是Graphics User Interface),平常你写的控制台程序叫Windows CUI程序(CUI, Console User Interface):






这样一个Windows建立好了。下面编译运行下:


随着你继续的学习,你可以给这个窗口添加更多的东西和功能,甚至可以自己绘制那种不规则的窗口和自定义背景。
我将代码做了点简化,贴在下面:

        1#include "stdafx.h"  2#include "FirstWindow.h"  3LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);  4// 程序入口:  5int APIENTRY _tWinMain(HINSTANCE hInstance,  6                     HINSTANCE hPrevInstance,  7                     LPTSTR    lpCmdLine,  8                     int       nCmdShow)  9{ 10 UNREFERENCED_PARAMETER(hPrevInstance); 11 UNREFERENCED_PARAMETER(lpCmdLine); 12 static TCHAR szClassName[] = _T("WindowsProgramTemplate"); 13 HWND hWnd; 14 MSG msg; 15 WNDCLASSEX wcex; 16 wcex.cbSize = sizeof(WNDCLASSEX); 17 wcex.style   = CS_HREDRAW | CS_VREDRAW; 18 wcex.lpfnWndProc = WndProc; 19 wcex.cbClsExtra  = 0; 20 wcex.cbWndExtra  = 0; 21 wcex.hInstance  = hInstance; 22 wcex.hIcon   = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_FIRSTWINDOW)); 23 wcex.hCursor  = LoadCursor(NULL, IDC_CROSS); 24 wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1); 25 //没有菜单 26 wcex.lpszMenuName = NULL; 27 wcex.lpszClassName = szClassName; 28 wcex.hIconSm  = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL)); 29 RegisterClassEx(&wcex); 30 hWnd = CreateWindow(szClassName,      //lpClassName  31      _T("Windows Program Template"),  //lpWindowName 32      WS_OVERLAPPEDWINDOW,    //dwStyle 33      CW_USEDEFAULT,      //x 34      CW_USEDEFAULT,      //y  35      CW_USEDEFAULT,      //nWidth 36      CW_USEDEFAULT,      //nHeight  37      NULL,        //hWndParent 38      NULL,        //hMenu  39      hInstance,       //hInstance  40      NULL);        //lParam 41   ShowWindow(hWnd, nCmdShow); 42   UpdateWindow(hWnd); 43 // 主消息循环: 44 while (GetMessage(&msg, NULL, 0, 0)) 45 { 46   TranslateMessage(&msg); 47   DispatchMessage(&msg); 48 } 49 return (int) msg.wParam; 50}// end  _tWinMain 51// 52//  函数: WndProc(HWND, UINT, WPARAM, LPARAM) 53// 54//  目的: 处理主窗口的消息。 55// 56//  WM_COMMAND - 处理应用程序菜单 57//  WM_PAINT - 绘制主窗口 58//  WM_DESTROY - 发送退出消息并返回 59// 60// 61LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) 62 { 63 HDC hdc; 64 PAINTSTRUCT ps; 65 switch (message) 66{  67 case WM_PAINT: 68  hdc = BeginPaint(hWnd, &ps); 69  // TODO: 在此添加任意绘图代码... 70  EndPaint(hWnd, &ps); 71  break; 72 case WM_DESTROY: 73  PostQuitMessage(0); 74  break; 75 default: 76  return DefWindowProc(hWnd, message, wParam, lParam); 77 } 78 return 0; 79}// end WndProc     

这样的代码对于新手看起来的确很难受,但是它的的确确是C程序呀。前面也说了,Windows程序通过typedef和define关键字定义了很多的新数据类型。下面我将为你一步步地分析这个程序的运行原理。

在我们写的CUI程序(控制台程序)函数的入口是main函数,但是其实这样说不贴切,因为这是在只考虑到像美国这样只使用英文的国家的情况下,传统C语言用一个字节去存储一个字符,这样最多只能表示2的8次方个字符,也就256的字符,可是像我们中国还有日本等亚洲国家光文字就不只256个,那么我们的汉字就无法编码进计算机中,可是Windows操作系统和C/C++毕竟在全世界广泛地流行着呀。好吧,让我再给你补充下C语言知识,让我们欢迎wchar_t类型,这个数据类型也是一个字符类型,和char类型一样,只不过它是占两个字节,传统的char类型占一个字节,请以一视同仁的态度对待wchar_t和char类型。

扩展下,因为本质上char类型与int类型无多大差别,毕竟在内存中都是数字。所以在C89标准中实现wchar_t时一般如下定义:


也就说wchar_t本质上是unsigned short,因为在32位机器上short型占2个字节(16位)。为了区别与char类型,我们把wchar_t称为宽字符,请从现在开始接受并使用它吧,它现在的的确确是C/C++中的基础数据类型。

所以如果是CUI程序,那么程序入口是_tmain()函数,_tmain()函数是何许人也?它本质又是一个宏:


UNICODE是编译器定义的一个宏,可以简单地理解是否需要支持宽字符,比如在我们国家肯定是要支持宽字符啦,不然汉字是不好显示的。
上面的代码表明,如果定义了UNICODE宏,那么_tmain在预编译的时候就被替换成wmain函数,反之,就被替换成main函数,但是请注意,由于替换是发生在预编译时期,所以只会有一个main函数版本。所以对于CUI程序,它的真正入口函数要么是wmain要么是main函数。同理,我们再看看GUI程序入口函数_tWinMain


所以GUI程序入口函数要么是wWinMain或者WinMain函数。我们阅读一个Windows GUI程序要从这个函数读起。
我们好好看看这个函数:

       1int APIENTRY _tWinMain(HINSTANCE hInstance, 2                     HINSTANCE hPrevInstance, 3                     LPTSTR    lpCmdLine, 4                     int       nCmdShow)     


Windows程序的入口函数
和控制台程序(CUI程序)一样,Windows GUI程序也有一个入口函数,要么是WinMain(),要么是wWinMain(),可以综合起来统一写成_tWinMain();而CUI程序的入口是main()或者wmain(),综合起来可以写成_tmain()。_tWinMain()函数原型如下:


这个函数是Windows GUI入口函数,我们编写程序都是从这个函数开始的。这个函数有四个参数,其中第四个参数的类型是我们熟悉的int型,返回值也是int型。还有两个陌生的类型HINSTANCE和LPTSTR。请不要害怕,我来帮你分析下。你会发现它们也是由C语言基础类型组装而来的。在这之前让我来说下那个APIENTRY,这是函数调用类型,它是用一个宏,对__stdcall的一个包装,在WinDef.h文件里有如下定义:





Windows程序的数据类型
我们来回顾下C语言中的数据类型,C语言中有以下常用数据类型:


就这么多,仅此而已。下面我们就利用这些基本的数据类型来组装成Windows中的数据类型:





根据以上说明,我们回过头来看_tWinMain()函数第三个参数类型: LPTSTR,我们可以将它当作PTSTR,PTSTR本质上就是char*或者wchar_t*类型。这样一分析,第三个参数是不是就明确了类型了呀。

Windows程序中的布尔类型
由于C语言中是不存在布尔类型的,所以微软就做了自己的布尔实现,如下文:


我个人建议,除非你使用Windows函数需要使用这种BOOL类型,否则,请使用C++中原生的bool类型,尤其是在一些逻辑判断时,这点我是从电驴的源代码中借鉴的:



电驴的源代码可以在verycd.com这个网站上下载到,打开这个网站搜索下就可以了,用的是mfc框架,如果你下载不了或者找不到可以向我索要。

说完这些简单数据类型以后,剩下的我们来说说Windows程序中最多的一个数据类型:句柄(HANDLE),而且句柄演化成各种具体的句柄类型。所谓句柄就是指向某块内存的一个指针,其外在表现形式可以简单地认为是某块内存的首地址,句柄用来对某种资源进行标识:


然后呢,这个HANDLE类型摇身一变成了各种具体的句柄类型。读者请看:


好吧,现在再回过头来看_tWinMain()函数的四个参数类型,第一二两个是HINSTANCE(void *),第三个是LPTSTR(char*或者wchar_t*), 最后一个是int型。

授人以鱼不如授人以渔,我们阅读或者刚刚开始学习编写Windows程序的时候,会遇到各种数据类型和Windows函数。我们不必害怕,可以通过下列方法学习它的用法,比如这个显示一个对话框的函数MessageBox():



当你看到这个MessageBox()函数时,如何去学习它的用法呢?
第一步:请打开微软官网上的MSDN(微软开发者社区),msdn.microsoft.com/或者microsoft.com/msdn/,默认打开的显示的语言是中文网页,建议换成英文的,英文资源更丰富一点,将地址栏中的网址msdn.microsoft.com/zh_c中的zh_cn替换成en_US就可以了。



看上面第二个图,你可以在左边设置搜索范围和搜索主题,微软站点引用了两个有名的国外技术站点的资源,一个是Stack Overflow(stackoverflow.org/),另外一个是Code Project(codeproject.com/)。

这里补充一点,这种搜索方法同样适合学习C#(当然我已经不建议学C#了,如果不是特别需要的话),仔细观察第二个图,你有没有发现搜索结果的第二项显示的是.net环境中的MessageBox类,这是用于C#中的的类,我们先点开第二搜索结果项看看:



在这个页面可以很清楚地看到C#中的MessageBox类的继承树、成员属性和成员函数。我对C#语言并不熟悉,但是通过阅读这个页面上的MessageBox.Show()方法,我可以断定,在C#中可以用这个方法显示一个对话框?对么?
我们学习C#中的某个类或者API函数,在百度或者Google上面搜索的结果也没有在这个地方搜索的结果权威和准确。建议学习C#的同学注意这一点。

回到正题上来,我们打开第一个搜索项,出现如下界面:



这个界面详细地介绍了Windows函数MessageBox的用法,甚至还说明了这个函数的注意事项和头文件、在哪个dll文件中实现、使用这个函数要求的最低系统版本要求:


比如根据这个页面的说明,我们可以将上面的消息框改成带三个按钮“取消”“重试”“继续”,图标样式为问号,第二个按钮是默认按钮的样式:


其代码是:



这篇文章到此也快结束了。我已经为你在C/C++和Windows GUI程序之间架设了一道桥梁。你也看到了如何利用C基础数据类型组建成Windows中的数据类型。如果你已经有了一定的C/C++基础,那么找一点简单的Windows程序来试着读一读改一改,或者找本Windows程序的入门书来看看。当然,Charles Petzold的《Windows程序设计》这本书我是强烈推荐的。虽然这本书出版年数已经久远了,而且Windows操作系统已经发展到Win10了,但是书中所讲述的Windows程序设计的原理和机制永远不过时。中国的老一代Windows程序员就是阅读这本书成长起来的,而他们此刻或在腾讯或在金山或在迅雷等大大小小的公司,编写着你正在使用的一个个软件呢。当然,遇到不认识或者不懂的函数按照上面的方法搜索学习吧。只要坚持,要不了多久,你就可以用C/C++写出很好看的带界面的程序。慢着,至于_tWinMain()函数的四个参数具体的用法,我这篇文章也不介绍了。其实,你也可以去msdn上搜索到,自己学习嘛。不过好的英语基础还是很重要的,用曹鹏老师的一句话:“英语是编程的霓裳”。 所以请学好英语。

题外话:

我上学的时候也研究过一些优质软件的界面库,现在把这些软件的源码整理出来分享给大家。

金山卫士源码

源码地址:链接: pan.baidu.com/s/1R48X4O 密码: 9jre

电驴

链接: pan.baidu.com/s/1xH-Wb3 密码: n5i0

开源 FTP 软件 —— filezilla

链接: pan.baidu.com/s/1YGe4a8 密码: 675q

user avatar   edliu 网友的相关建议: 
      

感谢

@sxc

邀请。非常非常感谢。

为了防止邀请我的sxc老师撤销邀请,我不得不截图。


@朱峰女士,你的答案,为了防止你进行修改,我已经截图了。没错,如你问题当中所说,礼貌是不是软弱?

当然不是。

我自问是一个普通人,在知乎得到关注多,也只是因为我勤勤恳恳,一个字一个字写得多,仅此而已。

我去咕咚网之前,当过记者,做过公关,我也不是什么名校毕业,但是我深深知道,原创是品德,是节操。做记者,报道要如实,要客观,要中立,要还原事情的本来面目。

我为什么要在微信群“红包体育”里面和你抬杠,为什么要质问你,想必你已经不记得了,然而我记得清清楚楚。


我不关注你的微信号,那是有非常重要的原因的。朱峰女士,你说你没做过亏心事,那么想必在你看来,未经他人许可引用、转载他人原创的内容,不算是亏心事了。


你不记得的事情,我一点一点帮你回忆起来吧。事情当然没有这么简单。

当你加入“红包体育”的时候,我对群主说了一句话。【我很高兴,我有不删除任何聊天软件当中聊天记录的好习惯。】


这里截图当中的日期是一直就存在的。至今我的iPhone 4S也一直在用呢,不可能改掉。


你为什么和我说抱歉,你忘了?2015年3月3日你所说的,是真的都不记得了?


当时我的反应,算是很克制的了,毕竟当着“红包体育”群里这么多人的面。

为什么我过了这么久,才再次在“红包体育”群里质问你,我想你应该明白。我知道每个人做自媒体不容易,想靠着才华变现,更加不容易,当时你肯道歉,说你会改,那么我也就得过且过了。


问题的关键在于,你改了吗?如果你改了,你就不会不经过

@式微

同意,转载她的答案,而且还将她列为“第二作者”。

你的所谓声明,夹杂在你的正文内容当中,而不是正式开辟一个子栏目道歉,被诸多的信息噪声遮盖着,这就是你的诚意?

上述三张截图,是2015年6月17日早上8:43时截的。我现在还很怕诸多水军说我图片造假呢。下面两张图,是2015年3月3日晚上20:49时截的。那个时候,你的微信ID还没有“太阳表情”。

这个总不能说我作假了吧?



而你在面对我的质疑的时候,说了些什么话,你还记得吗?这就是我为什么要截图的原因。

二次编辑加了些东西,就可以等同于你自己的原创,是吗?


事实证明我当初心一软得过且过,才是真的错误。


你说了“最初开时,格式内容混乱,但转载内容标明了作者”——我还是那句话:用了我的东西,问过我吗?

你说了“微信对于转载格式有了新要求后,我们也跟着学习,把之前来源不明的全部删除。之后再也没有出现不合规的转载“——来源不明?请看看截图,你自己说过的话,怎么就这么快忘了呢?”是从虎扑、知乎、直播吧很多来源的文章“,这还算是来源不明?

你说了“暴力行为冠以道德名义,缺又恰恰选择了一个认真做事的自媒体下手,无论是出于要稿费,还是炒作涨粉,都不会实现的”——暴力冠以道德的名义?我质问你,就是暴力,你不告而拿,拿了我的答案,也拿了知乎上别人的答案,这种偷窃行为,就是道德的?


另外,请弄清楚,到底谁在炒作?我只是把原文作者式微老师带到了“体育红包”群,让她自己和你说清楚,这就是炒作?式微维护自己正当权益没有成功,自己写了篇专栏,以正视听,这叫炒作?

你说了“另外。。。您在背后诽谤我的许多聊天截图我已经给了律师。我们没做亏心事,我们礼貌但不软弱,真的,用法律途径解决,只对我们单方面有利啊。但您若真的要这样苦苦相逼,请也不吝给我一个您的地址,给您去一封律师函”。


我在背后诽谤你?请把截图放出来,让知乎用户都看看,我到底怎么诽谤你了。


你没做亏心事?没做亏心事我会质问你为什么不经过我允许转载了我的内容?


说我苦苦相逼?到底谁逼谁?“咕咚-李旸”是我在“红包体育”群里的ID,那是因为之前说过要标清楚所在的企业、媒体和姓名,所以我这样写。


我再说一次:质问你,是因为你在知乎未经我许可,擅自转载和引用了我的内容;我质问你,是因为你在知乎未经式微老师的许可,擅自转载和引用了式微老师的内容。


知乎上的回答问题,是我业余时间所为,工作忙的时候我只能下班回答问题,晚上写公众号内容,或者把知乎的答案放到我自己的公众号上去。关于足球篮球的内容,和咕咚网没有一点关系,全部是我自己的业余创作。


而你,直接找到了咕咚创始人、CEO申波先生,也就是我的最高领导,去质问我的行为是代表咕咚,还是代表个人。


我在知乎的ID和个人说明写得清清楚楚,没有和咕咚有任何的关联。你没有经过我个人的允许,转载引用我在知乎的内容,被我质疑你转载了别人的内容,居然好意思说是“法律层面的诽谤”?居然还去和我供职的企业对质?


到底是谁苦苦相逼?


所谓认真做事的自媒体,是把知乎用户的文字答案,变成自己的声音和话语,放到视频当中去,是吗?


所谓认真做事的自媒体,是未经他人许可,擅自转载、引用他人在知乎的原创答案,是吗?



最后我很想问一句:你既然深知自媒体人的成长有多么不易,为什么你还要去做“未经许可,擅自转载和引用其他自媒体人的内容”这样的事情?


最后,是我放出的所有截图的具体信息。



我在这里声明:我是知乎用户李暘,在知乎的每一个答案,在知乎的每一篇专栏文章,不敢保证完美无缺,逻辑严密,没有错别字,但全部是我自己的原创内容,任何人未经我许可,转载、引用、抄袭我的答案,即为侵权行为。


user avatar   yiwang-er-shen-23 网友的相关建议: 
      

随夫姓不局限于发达国家,但全世界都大差不差。

婚后随夫姓,确实是体现了一种从属关系。如果按今天的政治正确,尤其是按女权思想,简直不可救药。而实际上,把目光放在历史长河当中,能随夫姓简直相当于进了体制内,属于铁饭碗待遇的体现!

苏轼牛不牛?其最爱的小妾,也就是被谪至黄州[1]都没有卖掉[2]的王朝云[3],坟头上也只能写着王氏,而非苏王氏[4]

图片出自百度百科

另外,关于姓氏,咱中国还略好一些,像日本,姓氏取的那么随意,也实在是没有办法!毕竟天皇到今天还没有姓氏[5],而平民拥有姓氏也只是近150年的事情[6]。姓氏上由于太随意,留下了很多奇葩的存在,例如"一二三、我孙子、肛门、猪鼻、上床、浮気[7]、土肥"……

名字就更随意了,男娃就叫大郎、次郎、三郎,至于山本五十六[8],咱也不敢说,咱也不敢问;女娃就是花子、美子、优子、菜子……并不比我们的大柱、二牛、小凤、翠花更高雅……

欧洲也没好到哪去,别看有些人名字里带个"Von/de/Don"之类的很显洋贵,其实11世纪之前这些贵族连姓都没有[9]。至于平民,那就更好办了,要么领个教姓/名[10],要么干脆看情况起一个,比如Fisher(鱼夫),Smith(铁匠),Hunter(猎人),Johnson(约翰之子),苹果CEO cook祖上大概率是厨子……

但毫无疑问的是,家庭作为社会中最基本的利益共同体,能否成为正式的家庭成员当然是极重要的硬指标。

在长久的旧时代,宗族/家族出身是极重要的,这是不分国界的。于女性而言,几乎不可能独立生存,如果再得不到家族的实名认证,人生大都是悲惨的。如果说有所区别,大概也只是悲惨的程度和花样。

现在回归问题身本:为什么如今在很多发达国家同样以随夫姓为主流呢?

先说,我国是破了四旧[11],烧了不知多少本家/族谱,否则也不会出现争冠姓权这种事。

一方面是惯性使然。这可不只是习惯问题,更不是简单的一句"传统观念"就能解释的。这种惯性,更多的是一种力量,是利益集团的一个标致,其中并非只有男权,而是整个家族的权力征徽,更类似于一家大企业的名号,其庇护作用是显而易见的。

而平民阶层随夫姓的主要原因是效仿权贵阶层,除了自觉拿到了"皇帝的金锄头",再就是"话事人[12]"原则的体现,也就是每家每户,都要有个撑门面的最强者,而普遍上兼具暴力与理性的男性显然更适合这个位置。就普通家庭而言,其实这个位置并没有多爽,责权一体不是说着玩的!如果放在中国,这个位置就更难坐了,很多中国家庭中享有冠姓权的男性,责任与实权、收益并不成正比。

这里面还有个逻辑推导,既从妻子的角度出发“我随他姓是因为他很强,也比我强,如果反过来,他随我姓就说明我比他强,反推就是他比我弱,如果我还不如多数随夫姓的女子,那则说明我们一家子都还不如别人家最弱的那个,那就没法混了呀!”

所以,姓氏对于小家庭意义并不大,但对家庭某一成员的意义很大。且随夫姓并不见得就亏,反而很可能是赚的,无利不起早,这符合人性中的趋利特点。

那一定有人问“为什么现在很多女性/女权还要争冠姓权呢?”

答案就是:不是姓氏有问题,而是人品有问题!


以上。

参考

  1. ^ 黄冈
  2. ^ 古时候妾属于财产,更接近于随意买卖交易的财物。
  3. ^ https://baike.baidu.com/item/%E7%8E%8B%E6%9C%9D%E4%BA%91/56511
  4. ^ 了解苏轼的应该知道,确有苏王氏,既其第二任妻子王弗。
  5. ^ 日本天皇是真的没有姓氏,虽自称是天照大神的后裔,但客观反应了中古时期(公元600年)之前的日本的蛮荒程度,这与人们普遍认知的日本文化相去甚远。
  6. ^ 明治三年(公元1870年)日本政府作出“凡国民,均可起姓”的决定。可是竟然没有多少人响应,于是,政府不得不在明治八年(1875年)颂布了强制性的《苗字必称令》,规定了“凡国民,必须起姓”。
  7. ^ 意为出轨。
  8. ^ 因其出生时父亲56岁。
  9. ^ 欧洲贵族自11世纪起才普遍以封地为姓氏,之前基本有名无姓。
  10. ^ 出自于宗教典籍,或牧师之类给起的姓氏
  11. ^ 指破除旧思想、旧文化、旧风俗、旧习惯,与其后的"立四新"相对应。
  12. ^ 指在一个组织中做出决定并承担责任,同时享有最高地位的人。



  

相关话题

  为什么 C++ std::map::operator[] 不提供 const 版本? 
  C++为何不允许在函数中直接传递数组? 
  为什么g++能够优化到动态库里的STL? 
  C++为何不允许在函数中直接传递数组? 
  游戏图标的设计跟其它应用的图标设计有何不同? 
  大括号不换行的坏处有什么?为什么有人不换行? 
  如何从只会 C++ 语法的水平到达完成项目编写软件的水平? 
  为什么说 Java 比 C++ 安全? 
  各种语言写网络爬虫有什么优点缺点? 
  为什么存在着size_t, LPCSTR, wchar_t等别名? 

前一个讨论
电动摩托和电动自行车有路权吗?
下一个讨论
为什么说 goto 是一种不好的用法?





© 2024-06-14 - tinynew.org. All Rights Reserved.
© 2024-06-14 - tinynew.org. 保留所有权利