【轉】為控制臺窗口建立消息隊列

介紹Windows的窗口、消息、子類化和超類化

這篇文章本來只是想介紹一下子類化和超類化這兩個比較“生僻”的名詞。為了敘述的完整性而討論了Windows的窗口和消息,也簡要討論了進程和線程。子類化(Subclassing)和超類化(Superclassing)是伴隨Windows窗口機制而產生的兩個復用代碼的方法。不要把“子類化、超類化”與面向對象語言中的派生類、基類混淆起來。“子類化、超類化”中的“類”是指Windows的窗口類。

0 運行程序

希望讀者在閱讀本節前先看看"談談Windows程序中的字符編碼"開頭的第0節和附錄0。第0節介紹了Windows系統的幾個重要模塊。附錄0概述了Windows的啟動過程,從上電到啟動Explorer.exe。本節介紹的是運行程序時發生的事情。

0.1 程序的啟動

當我們通過Explorer.exe運行一個程序時,Explorer.exe會調用CreateProcess函數請求系統為這個程序創建進程。當然,其它程序也可以調用CreateProcess函數創建進程。

系統在為進程分配內部資源,建立獨立的地址空間后,會為進程創建一個主線程。我們可以把進程看作單位,把線程看作員工。進程擁有資源,但真正在CPU上運行和調度的是線程。系統以掛起狀態創建主線程,即主線程創建好,不會立即運行,而是等待系統調度。系統向Win32子系統的管理員csrss.exe登記新創建的進程和線程。登記結束后,系統通知掛起的主線程可以運行,新程序才開始運行。

這時,在創建進程中CreateProcess函數返回;在被創建進程中,主線程在完成最后的初始化后進入程序的入口函數(Entry-point)。創建進程與被創建進程在各自的地址空間獨立運行。這時,即使我們結束創建進程,也不會影響被創建進程。

0.2 程序的執行

可執行文件(PE文件)的文件頭結構包含入口函數的地址。入口函數一般是Windows在運行時庫中提供的,我們在編譯時可以根據程序類型設定。在VC中編譯、運行程序的小知識點討論了Entry-point,讀者可以參考。

入口函數前的過程可以被看作程序的裝載過程。在裝載時,系統已經做過全局和靜態變量(在編譯時可以確定地址)的初始化,有初值的全局變量擁有了它們的初值,沒有初值的變量被設為0,我們可以在入口函數處設置斷點確認這一點。

進入入口函數后,程序繼續運行環境的建立,例如調用所有全局對象的構造函數。在一切就緒后,程序調用我們提供的主函數。主函數名是入口函數決定的,例如main或WinMain。如果我們沒有提供入口函數要求的主函數,編譯時就會產生鏈接錯誤。

0.3 進程和線程

我們通常把存儲介質(例如硬盤)上的可執行文件稱作程序。程序被裝載、運行后就成為進程。系統會為每個進程創建一個主線程,主線程通過入口函數進入我們提供的主函數。我們可以在程序中創建其它線程。

線程可以創建一個或多個窗口,也可以不創建窗口。系統會為有窗口的線程建立消息隊列。有消息隊列的線程就可以接收消息,例如我們可以用PostThreadMessage函數向線程發送消息。

沒有窗口的線程只要調用了PeekMessage或GetMessage,系統也會為它創建消息隊列。

1 窗口和消息

1.1 線程的消息隊列

每個運行的程序就是一個進程。每個進程有一個或多個線程。有的線程沒有窗口,有的線程有一個或多個窗口。

我們可以向線程發送消息,但大多數消息都是發給窗口的。發給窗口的消息同樣放在線程的消息隊列中。我們可以把線程的消息隊列看作信箱,把窗口看作收信人。我們在向指定窗口發送消息時,系統會找到該窗口所屬的線程,然后把消息放到該線程的消息隊列中。

線程消息隊列是系統內部的數據結構,我們在程序中看不到這個結構。但我們可以通過Windows的API向消息隊列發送、投遞消息;從消息隊列接收消息;轉換和分派接收到的消息。

1.2 最小的Windows程序

Windows的程序員大概都看過這么一個最小的Windows程序:

// 例程1 
#include "windows.h" 
static const char m_szName[] = "窗口"; 
////  
// 主窗口回調函數 如果直接用 DefWindowProc, 關閉窗口時不會結束消息循環 
static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{  switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); // 關閉窗口時發送WM_QUIT消息結束消息循環 break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); }  return 0; 
} 
////  
// 主函數  
int __stdcall WinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,LPSTR lpCmdLine, int nCmdShow) 
{ WNDCLASS wc;  memset(&wc, 0, sizeof(WNDCLASS)); wc.style = CS_VREDRAW|CS_HREDRAW; wc.lpfnWndProc = (WNDPROC)WindowProc; wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW); wc.lpszClassName = m_szName; RegisterClass(&wc); // 登記窗口類 
  HWND hWnd; hWnd = CreateWindow(m_szName,m_szName,WS_OVERLAPPEDWINDOW,100,100,320,240, NULL,NULL,hInstance,NULL); // 創建窗口 ShowWindow(hWnd, nCmdShow); // 顯示窗口 
  MSG sMsg;  while (int ret=GetMessage(&sMsg, NULL, 0, 0)) { // 消息循環 if (ret != -1) { TranslateMessage(&sMsg); DispatchMessage(&sMsg); } } return 0; 
} 

這個程序雖然只顯示一個窗口,但經常被用來說明Windows程序的基本結構。在MFC框架內部我們同樣可以找到類似的程序結構。這個程序包含以下基本概念:

  • 窗口類、窗口和窗口過程
  • 消息循環

下面分別介紹。

1.3 窗口類、窗口和窗口過程

創建窗口時要提供窗口類的名字。窗口類相當于窗口的模板,我們可以基于同一個窗口類創建多個窗口。我們可以使用Windows預先登記好的窗口類。但在更多的情況下,我們要登記自己的窗口類。在登記窗口類時,我們要登記名稱、風格、圖標、光標、菜單等項,其中最重要的就是窗口過程的地址。

窗口過程是一個函數。窗口收到的所有消息都會被送到這個函數處理。那么,發到線程消息隊列的消息是怎么被送到窗口的呢?

1.4 消息循環

熟悉嵌入式多任務程序的程序員,都知道任務(相當于Windows的線程)的結構基本上都是:

 while (1) { 等待信號; 處理信號; } 

任務收到信號就處理,否則就掛起,讓其它任務運行。這就是消息驅動程序的基本結構。Windows程序通常也是這樣:

while (int ret=GetMessage(&sMsg, NULL, 0, 0)) { // 消息循環 if (ret != -1) { TranslateMessage(&sMsg); DispatchMessage(&sMsg); } } 

GetMessage從消息隊列接收消息;TranslateMessage根據按鍵產生WM_CHAR消息,放入消息隊列;DispatchMessage根據消息中的窗口句柄將消息分發到窗口,即調用窗口過程函數處理消息。

1.5 通過消息通信

創建窗口的函數會返回一個窗口句柄。窗口句柄在系統范圍內(不是進程范圍)標識一個唯一的窗口實例。通過向窗口發送消息,我們可以實現進程內和進程間的通信。

我們可以用SendMessage或PostMessage向窗口發送或投遞消息。SendMessage必須等到目標窗口處理過消息才會返回。我試過:如果向一個沒有消息循環的窗口SendMessage,SendMessage函數永遠不會返回。PostMessage在把消息放入線程的消息隊列后立即返回。

其實只有投遞的消息才是通過DispatchMessage分派到窗口過程的。通過SendMessage發送的消息,在線程GetMessage時,就已經被分派到窗口過程了,不經過DispatchMessage。

1.5.1 窗口程序與控制臺程序的通信實例

大家是不是覺得“例程1”沒什么意思,讓我們用它來做個小游戲:讓“例程1”和一個控制臺程序做一次親密接觸。我們首先將“例程1”的窗口過程修改為:

static LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) 
{ static DWORD tid = 0; switch (uMsg) { case WM_DESTROY: PostQuitMessage(0); // 關閉窗口時發送WM_QUIT消息結束消息循環 break; case WM_USER: tid = wParam; // 保存控制臺程序的線程ID SetWindowText(hWnd, "收到"); break; case WM_CHAR: if (tid) { switch(wParam) { case '1': PostThreadMessage(tid, WM_USER+1, 0, 0); // 向控制臺程序發送消息1 break; case '2': PostThreadMessage(tid, WM_USER+2, 0, 0); // 向控制臺程序發送消息2 break; } } break; default: return DefWindowProc(hWnd, uMsg, wParam, lParam); }  return 0; 
} 
然后,我們創建一個控制臺程序,代碼如下:#include "windows.h" 
#include "stdio.h" 
static HWND m_hWnd = 0; 
void process_msg(UINT msg, WPARAM wp, LPARAM lp) 
{ char buf[100]; static int i = 1; if (!m_hWnd) { return; } switch (msg) { case WM_USER+1: SendMessage(m_hWnd, WM_GETTEXT, sizeof(buf), (LPARAM)buf); printf("你現在叫:%s\n\n", buf); // 讀取、顯示對方的名字 break; case WM_USER+2: sprintf(buf, "我是窗口%d", i++); SendMessage(m_hWnd, WM_SETTEXT, sizeof(buf), (LPARAM)buf); // 修改對方名字 printf("給你改名\n\n"); break; } 
} int main() 
{ MSG sMsg; printf("Start with thread id %d\n", GetCurrentThreadId()); m_hWnd = FindWindow(NULL,"窗口"); if (m_hWnd) { printf("找到窗口%x\n\n", m_hWnd); SendMessage(m_hWnd, WM_USER, GetCurrentThreadId(), 0); } else { printf("沒有找到窗口\n\n"); } while (int ret=GetMessage(&sMsg, NULL, 0, 0)) { // 消息循環 if (ret != -1) { process_msg(sMsg.message, sMsg.wParam, sMsg.lParam); } } return 0; 
}  

大家能看懂這游戲怎么玩嗎?首先運行“例程1”wnd,然后運行控制臺程序msg。msg會找到wnd的窗口,并將自己的主線程ID發給wnd。wnd收到msg的消息后,會顯示收到。這時,wnd和msg已經建立了通信的渠道:wnd可以向msg的主線程發消息,msg可以向wnd的窗口發消息。

我們如果在wnd窗口按下鍵'1',wnd會向msg發送消息1,msg收到后會通過WM_GETTEXT消息獲得wnd的窗口名稱并顯示。我們如果在wnd窗口按下鍵'2',wnd會向msg發送消息2,msg收到后會通過WM_SETTEXT消息修改wnd的窗口名稱。

這個小例子演示了控制臺程序的消息循環,向線程發消息,以及進程間的消息通信。

1.5.2 地址空間的問題

不同的進程擁有獨立的地址空間,如果我們在消息參數中包含一個進程A的地址,然后發送到進程B。進程B如果在自己的地址空間里操作這個地址,就會發生錯誤。那么,為什么上例中的WM_GETTEXT和WM_SETEXT可以正常工作?

這是因為WM_GETTEXT和WM_SETEXT都是Windows自己定義的消息,Windows知道參數的含義,并作了特殊的處理,即在進程B的空間分配一塊內存作為中轉,并在進程A和進程B的緩沖區之間復制數據。例如:在1.5.1節的例子中,如果我們設置斷點觀察,就會發現msg發送的WM_SETTEXT消息中的lParam不等于wnd接收到的WM_SETTEXT消息中的lParam。

如果我們在自己定義的消息中傳遞內存地址,系統不會做任何特殊處理,所以必然發生錯誤。

Windows提供了WM_COPYDATA消息用來向窗口傳遞數據,Windows同樣會為這個消息作特殊處理。

在進程間發送這些需要額外分配內存的消息時,我們應該用SendMessage,而不是PostMessage。因為SendMessage會等待接收方處理完后再返回,這樣系統才有機會額外釋放分配的內存。在這種場合使用PostMessage,系統會忽略要求投遞的消息,讀者可以在msg程序中試驗一下。

2 子類化和超類化

窗口類是窗口的模板,窗口是窗口類的實例。窗口類和每個窗口實例都有自己的內部數據結構。Windows雖然沒有公開這些數據結構,但提供了讀寫這些數據的API。

例如:用GetClassLong和SetClassLong函數可以讀寫窗口類的數據;用GetWindowLong和SetWindowLong可以讀寫指定窗口實例的數據。使用這些接口,可以在運行時讀取或修改窗口類或窗口實例的窗口過程地址。這些接口是子類化的實現基礎。

2.1 子類化

子類化的目的是在不修改現有代碼的前提下,擴展現有窗口的功能。它的思路很簡單,就是將窗口過程地址修改為一個新函數地址,新的窗口過程函數處理自己感興趣的消息,將其它消息傳遞給原窗口過程。通過子類化,我們不需要現有窗口的源代碼,就可以定制窗口功能。

子類化可以分為實例子類化和全局子類化。實例子類化就是修改窗口實例的窗口過程地址,全局子類化就是修改窗口類的窗口過程地址。實例子類化只影響被修改的窗口。全局子類化會影響在修改之后,按照該窗口類創建的所有窗口。顯然,全局子類化不會影響修改前已經創建的窗口。

子類化方法雖然是二十年前的概念,卻很好地實踐了面向對象技術的開閉原則(OCP:The Open-Closed Principle):對擴展開放,對修改關閉。

2.2 超類化

超類化的概念更簡單,就是讀取現有窗口類的數據,保存窗口過程函數地址。對窗口類數據作必要的修改,設置新窗口過程,再換一個名稱后登記一個新窗口類。新窗口類的窗口過程函數還是僅處理自己感興趣的消息,而將其它消息傳遞給原窗口過程函數處理。使用GetClassInfo函數可以讀取現有窗口類的數據。

3 MFC中的消息循環和子類化

MFC將子類化方法應用得淋漓盡致,是一個不錯的例子。候捷先生的《深入淺出MFC》已經將MFC的主要框架分析得很透徹了,本節只是看看MFC的消息循環,簡單分析MFC對子類化的應用。

3.1 消息循環

隨便建立一個MFC單文檔程序,在視圖類中添加WM_RBUTTONDOWN的處理函數,并在該處理函數中設置斷點。運行,斷下后,查看調用堆棧:

CHelloView::OnRButtonDown(unsigned int, CPoint) 
CWnd::OnWndMsg(unsigned int, unsigned int, long, long *) 
CWnd::WindowProc(unsigned int, unsigned int, long) 
AfxCallWndProc(CWnd *, HWND__ *, unsigned int, unsigned int, long) 
AfxWndProc(HWND__ *, unsigned int, unsigned int, long) 
AfxWndProcBase(HWND__ *, unsigned int, unsigned int, long) 
USER32! 7e418734() 
USER32! 7e418816() 
USER32! 7e4189cd() 
USER32! 7e4196c7() 
CWinThread::PumpMessage() 
CWinThread::Run() 
CWinApp::Run() 
AfxWinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int) 
WinMain(HINSTANCE__ *, HINSTANCE__ *, char *, int) 
WinMainCRTStartup() 
KERNEL32! 7c816fd7()  
WinMainCRTStartup是這個程序的入口函數。候捷先生已經詳細介紹過AfxWinMain。我們就看看CWinThread::PumpMessage中的消息循環:BOOL CWinThread::PumpMessage() 
{ if (!::GetMessage(&m_msgCur, NULL, NULL, NULL)) { return FALSE; } if (m_msgCur.message != WM_KICKIDLE && !PreTranslateMessage(&m_msgCur)) { ::TranslateMessage(&m_msgCur); ::DispatchMessage(&m_msgCur); } return TRUE; 
} 

這就是MFC程序主線程中的消息循環,它把發送到線程消息隊列的消息分派到線程的窗口。

3.2 子類化

CWnd::CreateEx在創建窗口前調用SetWindowsHookEx函數安裝了一個鉤子函數_AfxCbtFilterHook。窗口剛創建好,鉤子函數_AfxCbtFilterHook就被調用。_AfxCbtFilterHook調用SetWindowLong將窗口過程替換為AfxWndProcBase,并將SetWindowLong返回的原窗口地址保存到成員變量oldWndProc。上節調用堆棧中的AfxWndProcBase就是由此而來。

可見,通過CWnd::CreateEx創建的所有窗口都會被子類化,即它們的窗口過程都會被替換為AfxWndProcBase。MFC為什么要這樣做?

讓我們再看看調用堆棧中的CWnd::WindowProc函數:

LRESULT CWnd::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{ LRESULT lResult = 0; if (!OnWndMsg(message, wParam, lParam, &lResult)) lResult = DefWindowProc(message, wParam, lParam); return lResult; 
} 

按照侯捷先生的介紹,CWnd::OnWndMsg就是“MFC消息泵”的入口,消息通過這個入口流入MFC消息映射中的消息處理函數。消息泵只會處理我們定制過的消息,我們沒有添加過處理的消息會原封不動地流過"消息泵",進入DefWindowProc函數。在DefWindowProc函數中,消息會傳給子類化時保存的原窗口地址oldWndProc。

CWnd::CreateEx里的鉤子會子類化所有窗口嗎?其實不盡然。的確,MFC所有窗口相關的類都是從CWnd派生的,這些類的實例在創建窗口時都會調用CWnd::CreateEx,都會被子類化。但是,通過對話框模板創建的窗口是通過CreateDlgIndirect創建的,不經過CWnd::CreateEx函數。

但這點其實也不是問題,因為如果我們想通過MFC定制一個控件的消息映射,就必須先子類化這個控件,MFC還是有機會將窗口過程替換成自己的AfxWndProcBase。下一節將介紹對話框控件的子類化。

4 子類化和超類化的例子

我寫了一個很簡單的對話框程序,用來演示子類化和超類化。這個對話框程序有兩個編輯框,我將編輯框的右鍵菜單換成了一個消息框。兩個編輯框的定制分別采用了子類化和超類化技術:

?

4.1 子類化的例子

首先從CEdit派生出CMyEdit1,定制WM_RBUTTONDOWN的處理。很多文章都建議我們在對話框的OnInitDialog中用SubclassDlgItem實現子類化:

 m_edit1.SubclassDlgItem(IDC_EDIT1, this); 

這樣做當然可以。其實如果我們已經為IDC_EDIT1添加過CMyEdit1對象:

void CSubclassingDlg::DoDataExchange(CDataExchange* pDX) 
{ 
 CDialog::DoDataExchange(pDX); 
 //{{AFX_DATA_MAP(CSubclassingDlg) 
 DDX_Control(pDX, IDC_EDIT1, m_edit1); 
 //}}AFX_DATA_MAP 
} 

DDX_Control會自動幫我們完成子類化,沒有必要手工調用SubclassDlgItem。大家可以通過在PreSubclassWindow中設置斷點看看。

通過DDX_Control或者SubclassDlgItem子類化控件的效果是一樣的,MFC都是把窗口過程替換成AfxWndProcBase。用戶添加過處理函數的消息通過MFC消息泵流入用戶的處理函數。

4.2 必經之路:PreSubclassWindow

PreSubclassWindow是一個很好的定制控件的位置。如果我們通過重載CWnd::PreCreateWindow定制控件,而用戶在對話框中使用控件。由于對話框中的控件窗口是通過CreateDlgIndirect創建,不經過CWnd::CreateEx函數,PreCreateWindow函數不會被調用。

其實,用戶要在對話框中使用定制控件,必須用DDX或者SubclassDlgItem函數子類化控件,這時PreSubclassWindow一定會被調用。

如果用戶直接創建定制控件窗口,CWnd::CreateEx函數就一定會被調用,控件窗口一定會被子類化以安裝MFC消息泵。所以在MFC中,PreSubclassWindow是創建窗口的必經之路。

4.3 超類化的例子

我很少看到超類化的例子(除了羅云彬的Win32匯編),在大多數應用中,子類化技術已經足夠了。但我還是寫了一個例子:CMyEdit2從CEdit派生。CMyEdit2::RegisterMe獲取窗口類Edit的信息,保存原窗口過程,設置新窗口過程MyWndProc和新名稱MyEdit,登記一個新窗口類。新窗口過程MyWndProc定制自己需要處理的消息,將其它消息送回原窗口過程。

我在對話框的OnInitDialog中先調用CMyEdit2::RegisterMe登記新窗口類,然后創建窗口。這樣創建窗口必須經過CWnd::CreateEx,所以MFC還是會把窗口過程換成AfxWndProcBase。沒有被MFC消息映射攔截的消息才會流入MyWndProc。

5 結束語

這篇文章介紹了一些Windows和MFC的基礎知識。寫這篇文章的目的不是介紹什么編程技巧,而是讓我們更了解程序運行時發生的事情。惟有深入其中,方能跳出其外,不受羈絆。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/397499.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/397499.shtml
英文地址,請注明出處:http://en.pswp.cn/news/397499.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

hightmaps 按地圖上顯示的統計數據

離extjs 至 easyui 到html5到hightchars 再到hightmaps。Exjts和easyui很相似,extjs是重量級的,easyui輕量級的。比extjs容易上手。照著demo改就能夠開發了。easyui入門demo見:easyui-demo,或者到官網http://www.jeasyui.com/&…

python pytorch 版本,python 如何查看pytorch版本

看代碼吧~import torchprint(torch.__version__)補充:pytorch不同版本安裝以及版本查看一:基于conda安裝conda create --name pytorch_learn python3.6.7#創建一個名為pytorch_learn的環境source activate pytorch_learn #進入環境conda install pytorch…

Unity WebGL 窗口自適應

unity 打包好WebGL后&#xff0c;用文本編輯器編輯打包生成的 index.html 文件 在生成的html里面修改代碼<script type"text/javascript">    function Reset() {       var canvas document.getElementById("#canvas");        …

python 會增加內存嗎,Python+不斷增加的內存分配

我正在寫一個模塊來訓練一個大型數據集上的ML模型——它包括0.6米的數據點&#xff0c;每個數據點的維度都是0.15米。我在加載數據集本身時遇到了問題。(全是numpy數組)下面是一個代碼片段(它復制了實際代碼的主要行為)&#xff1a;import numpyimport psutilFV_length 150000…

非IT人士的云棲醬油之行 (程序猿迷妹的云棲之行)

摘要&#xff1a; 熟悉我的人都知道&#xff0c;我是一個貪玩兒且不學無術的姑娘&#xff0c;對于互聯網我也是知之甚少&#xff1b;這次去到杭州參加阿里巴巴集團主辦的為期4天的科技大會也是很例外&#xff1b;但是不得不說這次的會議真是讓我很震驚。今天我就和大家分享一下…

MySQL 全文搜索支持, mysql 5.6.4支持Innodb的全文檢索和類memcache的nosql支持

背景&#xff1a;搞個個人博客的全文搜索得用like啥的&#xff0c;現在mysql版本號已經大于5.6.4了也就支持了innodb的全文搜索了&#xff0c;剛查了下目前版本號都到MySQL Community Server 5.6.19 了&#xff0c;所以&#xff0c;一些小的應用可以用它做全文搜索了&#xff0…

搭建基于Jenkins的CI服務器

安裝Jenkins和創建任務這些操作網上一搜一大把&#xff0c;這里就沒必要寫了&#xff0c;直接就開始編譯、單元測試&#xff0c;覆蓋&#xff0c;git提交觸發構建&#xff0c;構建失敗發送給提交人郵件。 因為項目比較復雜&#xff0c;為了懶省事我直接在CI服務器上安裝了visua…

php打補丁,PHPMailer庫打補丁后漏洞仍然存在,怎么解?

開源PHPMailer庫被披露存有一個嚴重的遠程代碼執行漏洞。這個漏洞在被修補后&#xff0c;又進行了二次修復&#xff0c;因為第一次沒有充分解決問題。那么&#xff0c;這個漏洞是如何工作的&#xff1f;為什么原始補丁沒有解決問題&#xff1f;Michael Cobb&#xff1a;代碼庫和…

Ubuntu下安裝jdk經驗分享

Ubuntu下安裝jdk經驗分享http://www.jb51.net/article/55131.htm轉載于:https://www.cnblogs.com/kangtuohongwai/p/6002555.html

BZOJ 1270: [BeijingWc2008]雷濤的小貓( dp )

簡單的dp..dp(i,j) max(dp(x,y))cnt[i][j], (x,y)->(i,j)是合法路徑.設f(i) max(dp(x,y))(1≤x≤N, 1≤y≤i), g(i,j) max(dp(i, k))(1≤k≤j)那么dp(i,j) max(f(jdelta), g(i,j1))cnt[i][j]. 遞推即可. 時間復雜度O(NH)----------------------------------------------…

【校招面試 之 C/C++】第12題 C++ 重載、重寫和重定義

1、成員函數重載特征&#xff1a; a.相同的范圍&#xff08;在同一個類中&#xff09;&#xff1b; b.函數名字相同&#xff1b; c.參數不同&#xff08;參數個數不同或者參數類型不同&#xff0c;但是返回值不同不能使重載&#xff09;&#xff1b; d.virtual關鍵字可有可無…

mac php5.6.30與php7共存,認識Homebrew以及在Mac上同時安裝PHP5及PHP7

Homebrew幾乎是Mac上必備的軟件&#xff0c;用于下載安裝和管理其他軟件。尤其對于程序員&#xff0c;講真&#xff0c;本人到現在仍然不知道在Mac上如何不借助Homebrew來搭建php-apache-mysql開發環境。認識HomebrewHomebrew是一個開源項目&#xff0c;據說它的作者曾經去谷歌…

POJ 1141

題意&#xff1a;給出一個表達式的子序列&#xff0c;要你填充這個序列&#xff0c;保證最終形成的序列長度最短&#xff0c;也就是添加的括號最少 這個子序列要遵循括號匹配的原則。 分析&#xff1a;轉移方程dp[i][j]min(dp[i][k],dp[k1][j]).i<k<j.dp[1][1]1; dp[i][j…

PHP array_count_values() 函數用于統計數組中所有值出現的次數。

定義和用法 array_count_values() 函數用于統計數組中所有值出現的次數。 本函數返回一個數組&#xff0c;其元素的鍵名是原數組的值&#xff0c;鍵值是該值在原數組中出現的次數。 語法 array_count_values(array) 參數 描述 array 必需。規定輸入的數組。 例子 <?php …

SpringDay01

一&#xff1a;什么是Spring。 簡單的理解就是一個可以裝web層&#xff0c; service層&#xff0c; dao層&#xff0c;這三層對象的容器。 二&#xff1a;Spring搭建 1.導包&#xff1a;核心四個包和log4j兩個包 2.注冊對象&#xff1a;User類 3.書寫配置注冊對象到容器 a>導…

bom_clear.php,thinkphp清除BOM方法

該樓層疑似違規已被系統折疊 隱藏此樓查看此樓在utf-8編碼文件中BOM在文件頭部&#xff0c;占用三個字節&#xff0c;用來標示該文件屬于utf-8編碼&#xff0c;現在已經有很多軟件識別bom頭&#xff0c;但是還有些不能識別bom頭&#xff0c;比如PHP就不能識別bom頭&#xff0c;…

(算法)Trapping Rain Water I

題目&#xff1a; Given n non-negative integers representing an elevation map where the width of each bar is 1, compute how much water it is able to trap after raining. For example, Given [0,1,0,2,1,0,1,3,2,1,2,1], return 6. 思路&#xff1a; 題目的意思是說&…

字符數組拷貝與strcpy函數

代碼&#xff1a; char str1[10],str2[10];for (int i0;i<10;i){str1[i]a;}strcpy(str2,str1); 讓找出錯誤的地方。 先來看下strcpy函數&#xff1a; 使用格式&#xff1a;char* strcmp&#xff08;char* buffer&#xff0c;char*str&#xff09;功 能: 把從str地址開始且含…

java中的NAN和INFINITY

2019獨角獸企業重金招聘Python工程師標準>>> java浮點數運算中有兩個特殊的情況&#xff1a;NAN、INFINITY。 1、INFINITY&#xff1a; 在浮點數運算時&#xff0c;有時我們會遇到除數為0的情況&#xff0c;那java是如何解決的呢&#xff1f; 我們知道&#xff0c;在…

php框架tp5工作流程,tp5框架流程

之前沒怎么了解過&#xff0c;但用過TP3.2.網上查了下說是區別很大&#xff0c;特此記錄下。流程&#xff1a;1.入口文件默認是 public目錄下的index.php// 定義應用目錄define(APP_PATH, __DIR__ . /../application/);// 加載框架引導文件require __DIR__ . /../thinkphp/star…