1。先來(lái)介紹REPORT類型的CListCtrl: 首先使用下面的語(yǔ)句設(shè)置CListCtrl的style: DWORD SetExtendedStyle( DWORD dwNewStyle ); 其中 LVS_EX_CHECKBOXES 表示添加CheckBox LVS_EX_FULLROWSELECT 表示選擇整行 LVS_EX_GRIDLINES 表示添加表格線
如果設(shè)置了LVS_EX_CHECKBOXES屬性,則可以用 BOOL GetCheck( int nItem ) const; 來(lái)得到某一行是否Checked。
可以先用下面的語(yǔ)句來(lái)刪除以前的東西: for(int k=2;k>=0;k--) //注意要從后往前刪,否則出錯(cuò) m_ListCtrl.DeleteColumn(k); m_ListCtrl.DeleteAllItems();
用下面的語(yǔ)句新建列: m_ListCtrl.InsertColumn(0,_T("文件名"),LVCFMT_IMAGE|LVCFMT_LEFT); m_ListCtrl.InsertColumn(1,_T("儀器類別")); m_ListCtrl.InsertColumn(2,_T("項(xiàng)目類別")); 其中LVCFMT_IMAGE表示可以在第一列加入圖標(biāo)。如果不要圖標(biāo)可以刪去。
然后設(shè)置列寬: for(j=0;j<3;j++) m_ListCtrl.SetColumnWidth(j ,100); 以下為列表加入圖標(biāo),如果不需要圖標(biāo),可以跳過(guò)這一步。注意只在第一次加入,如果多次加入會(huì)出錯(cuò)! 先在頭文件中加入聲明: CImageList m_ImageList; 這是必要的,如果在cpp的某個(gè)函數(shù)中加入由于生命期結(jié)束,CImageList自動(dòng)釋放,則效果是列表中看不到圖標(biāo),只看到一個(gè)白方塊。 下面生成CImageList,并將其綁定到CListCtrl中,這是CImageList中還沒(méi)有圖標(biāo),只是一個(gè)容器: static int flag=2; if(flag==2){//只調(diào)用一次SetImageList,否則出錯(cuò) m_ImageList.Create(128, 128, ILC_COLORDDB|ILC_MASK, 20, 1); m_ListCtrl.SetImageList(&m_ImageList,LVSIL_SMALL); } flag=(flag+1)%2; 如果CListCtrl已經(jīng)用過(guò),曾經(jīng)加過(guò)圖標(biāo)進(jìn)去,這時(shí)就要?jiǎng)h除上次放進(jìn)m_ImageList中的Image for(int kk=0;kk<m_ImageList.GetImageCount();kk++) m_ImageList.Remove(k); 下面介紹如何向CListCtrl里面加入行,并同時(shí)為每一行動(dòng)態(tài)加入圖標(biāo): 假設(shè)m_listRowCount為要加入的行數(shù)。 CBitmap* bitmap; bitmap=new CBitmap[m_list1rowCount]; HBITMAP hbitmap; for(int i = 0; i < m_listRowCount; i++) { //為每一行插入相應(yīng)的縮略圖 CFile f; CFileException e; if( !f.Open(m_FileName, CFile::modeRead, &e )){ //m_FileName為bmp文件名,由你來(lái)定 hbitmap = (HBITMAP)LoadImage(NULL,path+"blank.bmp",IMAGE_BITMAP,0,0, LR_CREATEDIBSECTION|LR_DEFAULTSIZE|LR_LOADFROMFILE); }else{ f.Close(); hbitmap = (HBITMAP)LoadImage(NULL,bmpFile,IMAGE_BITMAP,0,0, LR_CREATEDIBSECTION|LR_DEFAULTSIZE|LR_LOADFROMFILE); } bitmap[i].Attach(hbitmap); m_ImageList.Add(&bitmap[i], RGB(0, 128, 128)); //插入行 m_ListCtrl.InsertItem(i,m_FileName,i); m_ListCtrl.SetItemText(i,1,type); m_ListCtrl.SetItemText(i,2,m_Path); } //記得刪除已經(jīng)沒(méi)用的臨時(shí)文件 if(m_list1rowCount!=0) delete[] bitmap;
2。如果是ICON類型的CListCtrl,則要做一點(diǎn)點(diǎn)改動(dòng): 把綁定圖標(biāo)集的代碼由 SetImageList(&m_ImageList,LVSIL_SMALL); 改為 SetImageList(&m_ImageList,LVSIL_NORMAL);
插入行時(shí)只用 InsertItem(i,mainSet.m_FileName,i); 不用 SetItemText(i,1,type); 之類的代碼。
|
設(shè)置報(bào)表的樣式
選中一整行:
m_list_ctrl.SetExtendedStyle(m_list_ctrl.GetExtendedStyle()|LVS_EX_FULLROWSELECT);
繪制表格:
m_list_ctrl.SetExtendedStyle(m_list_ctrl.GetExtendedStyle()|LVS_EX_GRIDLINES);
帶復(fù)選框:
m_list_ctrl.SetExtendedStyle(m_list_ctrl.GetExtendedStyle()|LVS_EX_CHECKBOXES);
自動(dòng)切換:
m_list_ctrl.SetExtendedStyle(m_list_ctrl.GetExtendedStyle()|LVS_EX_TRACKSELECT);
選定一行:
設(shè)置CListCtrl的Show selection always選項(xiàng)
SetItemState (iIndex, LVIS_SELECTED|LVIS_FOCUSED, LVIS_SELECTED|LVIS_FOCUSED)
選中一個(gè)或多個(gè)項(xiàng)目時(shí),會(huì)發(fā)送LVN_ITEMCHANGED消息,可以使用
GetSelectedCount()方法得到被選定的項(xiàng)的數(shù)目。
點(diǎn)擊列頭的消息響應(yīng):
ON_NOTIFY(HDN_ITEMCLICKW, 0, ResponseFunc)
消息,需要自己添加
或者:
ON_NOTIFY(LVN_COLUMNCLICK, ID_yourCtrl, ResponseFunc)//向?qū)砑?br>前者后響應(yīng),后者先響應(yīng)
響應(yīng)函數(shù):
ResponseFunc(NMHDR *pNMHDR, LRESULT *pResult)
雙擊CListCtrl中的ITEM的消息是及消息函數(shù):
ON_NOTIFY(NM_DBLCLK, ID_yourCtrl, ResponseFunc)
單擊ITEM的消息響應(yīng):
ON_NOTIFY(NM_CLICK, ID_yourCtrl, ResponseFunc)
ResponseFunc(NMHDR *pNMHDR, LRESULT *pResult)
HDN_ITEMCLICK 就是Header control Notify message for mouse left click on the Header control!
而HDN_ITEMCLICK是當(dāng)List View中存在一個(gè)Header Contrl時(shí),Header Ctrl通知父窗口List View的!
CListCtrl中的Item被選中觸發(fā)LBN_SELCHANGE(通過(guò)WM_COMMAND)消息!
刪除CListCtrl中選定的項(xiàng):
POSITION pos;
int nIndex;
for(; pos= GetFirstSelectedItemPosition();)
{
nIndex = GetNextSelectedItem(pos);
DeleteItem(nIndex);
}
在ListCtrl中進(jìn)行排序
列表控件(CListCtrl)的頂部有一排按鈕,用戶可以通過(guò)選擇不同的列來(lái)對(duì)記錄進(jìn)行排序。但是 CListCtrl并沒(méi)有自動(dòng)排序的功能,我們需要自己添加一個(gè)用于排序的回調(diào)函數(shù)來(lái)比較兩個(gè)數(shù)據(jù)的大小,此外還需要響應(yīng)排序按鈕被點(diǎn)擊的消息。下面講述一下具體的做法。
CListCtrl提供了用于排序的函數(shù),函數(shù)原型為:BOOL CListCtrl::SortItems( PFNLVCOMPARE pfnCompare, DWORD dwData )。其中第一個(gè)參數(shù)為全局排序函數(shù)的地址,第二個(gè)參數(shù)為用戶數(shù)據(jù),你可以根據(jù)你的需要傳遞一個(gè)數(shù)據(jù)或是指針。該函數(shù)返回-1代表第一項(xiàng)排應(yīng)在第二項(xiàng)前面,返回1代表第一項(xiàng)排應(yīng)在第二項(xiàng)后面,返回0代表兩項(xiàng)相等。
用于排序的函數(shù)原形為:int CALLBACK ListCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort),其中第三個(gè)參數(shù)為調(diào)用者傳遞的數(shù)據(jù)(即調(diào)用SortItems時(shí)的第二個(gè)參數(shù)dwData)。第一和第二個(gè)參數(shù)為用于比較的兩項(xiàng)的ItemData,你可以通過(guò)DWORD CListCtrl::GetItemData( int nItem )/BOOL CListCtrl::SetItemData( int nItem, DWORD dwData )來(lái)對(duì)每一項(xiàng)的ItemData進(jìn)行存取。在添加項(xiàng)時(shí)選用特定的CListCtrl::InsertItem也可以設(shè)置該值。由于你在排序時(shí)只能通過(guò)該值來(lái)確定項(xiàng)的位置所以你應(yīng)該比較明確的確定該值的含義。
最后一點(diǎn),我們需要知道什么時(shí)候需要排序,實(shí)現(xiàn)這點(diǎn)可以在父窗口中對(duì)LVN_COLUMNCLICK消息進(jìn)行處理來(lái)實(shí)現(xiàn)。
下面我們看一個(gè)例子,這個(gè)例子是一個(gè)派生類,并支持順序/倒序兩種方式排序。為了簡(jiǎn)單我對(duì)全局?jǐn)?shù)據(jù)進(jìn)行排序,而在實(shí)際應(yīng)用中會(huì)有多組需要排序的數(shù)據(jù),所以需要通過(guò)傳遞參數(shù)的方式來(lái)告訴派序函數(shù)需要對(duì)什么數(shù)據(jù)進(jìn)行排序。
//全局?jǐn)?shù)據(jù)
struct DEMO_DATA
{
char szName[20];
int iAge;
}strAllData[5]={{"王某",30},{"張某",40},{"武某",32},{"陳某",20},{"李某",36}};
//CListCtrl派生類定義
class CSortList : public CListCtrl
{
// Construction
public:
CSortList();
BOOL m_fAsc;//是否順序排序
int m_nSortedCol;//當(dāng)前排序的列
protected:
//{{AFX_MSG(CSortList)
//}}AFX_MSG
...
};
//父窗口中包含該CListCtrl派生類對(duì)象
class CSort_in_list_ctrlDlg : public CDialog
{
// Construction
public:
CSort_in_list_ctrlDlg(CWnd* pParent = NULL); // standard constructor
// Dialog Data
//{{AFX_DATA(CSort_in_list_ctrlDlg)
enum { IDD = IDD_SORT_IN_LIST_CTRL_DIALOG };
CSortList m_listTest;
//}}AFX_DATA
}
//在父窗口中定義LVN_COLUMNCLICK消息映射
BEGIN_MESSAGE_MAP(CSort_in_list_ctrlDlg, CDialog)
//{{AFX_MSG_MAP(CSort_in_list_ctrlDlg)
ON_NOTIFY(LVN_COLUMNCLICK, IDC_LIST1, OnColumnclickList1)
//}}AFX_MSG_MAP
END_MESSAGE_MAP()
//初始化數(shù)據(jù)
BOOL CSort_in_list_ctrlDlg::OnInitDialog()
{
CDialog::OnInitDialog();
//初始化ListCtrl中數(shù)據(jù)列表
m_listTest.InsertColumn(0,"姓名");
m_listTest.InsertColumn(1,"年齡");
m_listTest.SetColumnWidth(0,80);
m_listTest.SetColumnWidth(1,80);
for(int i=0;i<5;i++)
{
m_listTest.InsertItem(i,strAllData[i].szName);
char szAge[10];
sprintf(szAge,"%d",strAllData[i].iAge);
m_listTest.SetItemText(i,1,szAge);
//設(shè)置每項(xiàng)的ItemData為數(shù)組中數(shù)據(jù)的索引
//在排序函數(shù)中通過(guò)該ItemData來(lái)確定數(shù)據(jù)
m_listTest.SetItemData(i,i);
}
return TRUE; // return TRUE unless you set the focus to a control
}
//處理消息
void CSort_in_list_ctrlDlg::OnColumnclickList1(NMHDR* pNMHDR, LRESULT* pResult)
{
NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;
//設(shè)置排序方式
if( pNMListView->iSubItem == m_listTest.m_nSortedCol )
m_listTest.m_fAsc = !m_listTest.m_fAsc;
else
{
m_listTest.m_fAsc = TRUE;
m_listTest.m_nSortedCol = pNMListView->iSubItem;
}
//調(diào)用排序函數(shù)
m_listTest.SortItems( ListCompare, (DWORD)&m_listTest );
*pResult = 0;
}
//排序函數(shù)實(shí)現(xiàn)
int CALLBACK ListCompare(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
//通過(guò)傳遞的參數(shù)來(lái)得到CSortList對(duì)象指針,從而得到排序方式
CSortList* pV=(CSortList*)lParamSort;
//通過(guò)ItemData來(lái)確定數(shù)據(jù)
DEMO_DATA* pInfo1=strAllData+lParam1;
DEMO_DATA* pInfo2=strAllData+lParam2;
CString szComp1,szComp2;
int iCompRes;
switch(pV->m_nSortedCol)
{
case(0):
//以第一列為根據(jù)排序
szComp1=pInfo1->szName;
szComp2=pInfo2->szName;
iCompRes=szComp1.Compare(szComp2);
break;
case(1):
//以第二列為根據(jù)排序
if(pInfo1->iAge == pInfo2->iAge)
iCompRes = 0;
else
iCompRes=(pInfo1->iAge < pInfo2->iAge)?-1:1;
break;
default:
ASSERT(0);
break;
}
//根據(jù)當(dāng)前的排序方式進(jìn)行調(diào)整
if(pV->m_fAsc)
return iCompRes;
else
return iCompRes*-1;
}
排序最快:
CListCtrl::SortItems
Example
// Sort the item in reverse alphabetical order.
static int CALLBACK
MyCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort)
{
// lParamSort contains a pointer to the list view control.
// The lParam of an item is just its index.
CListCtrl* pListCtrl = (CListCtrl*) lParamSort;
CString strItem1 = pListCtrl->GetItemText(lParam1, 0);
CString strItem2 = pListCtrl->GetItemText(lParam2, 0);
return strcmp(strItem2, strItem1);
}
void snip_CListCtrl_SortItems()
{
// The pointer to my list view control.
extern CListCtrl* pmyListCtrl;
// Sort the list view items using my callback procedure.
pmyListCtrl->SortItems(MyCompareProc, (LPARAM) pmyListCtrl);
}
If you don’t want to allow the users to sort the list by clicking on the header, you can use the style LVS_NOSORTHEADER. However, if you do want to allow sorting, you do not specify the LVS_NOSORTHEADER. The control, though, does not sort the items. You have to handle the HDN_ITEMCLICK notification from the header control and process it appropriately. In the code below, we have used the sorting function SortTextItems() developed in a previous section. You may choose to sort the items in a different manner.
Step 1: Add two member variables
Add two member variables to the CListCtrl. The first variable to track which column has been sorted on, if any. The second variable to track if the sort is ascending or descending.
int nSortedCol;
BOOL bSortAscending;
Step 2: Initialize them in the constructor.
Initialize nSortedCol to -1 to indicate that no column has been sorted on. If the list is initially sorted, then this variable should reflect that.
nSortedCol = -1;
bSortAscending = TRUE;
Step 3: Add entry in message map to handle HDN_ITEMCLICK
Actually you need to add two entries. For HDN_ITEMCLICKA and HDN_ITEMCLICKW. Do not use the class wizard to add the entry. For one, you need to add two entries whereas the class wizard will allow you only one. Secondly, the class wizard uses the wrong macro in the entry. It uses ON_NOTIFY_REFLECT() instead of ON_NOTIFY(). Since the HDN_ITEMCLICK is a notification from the header control to the list view control, it is a direct notification and not a reflected one.
ON_NOTIFY(HDN_ITEMCLICKA, 0, OnHeaderClicked)
ON_NOTIFY(HDN_ITEMCLICKW, 0, OnHeaderClicked)
Note that we specify the same function for both the notification. Actually the program will receive one or the other and not both. What notification it receives will depend on the OS. The list view control on Windows 95 will send the ANSI version and the control on NT will send the UNICODE version.
Also, note that the second argument is zero. This value filters for the id of the control and we know that header control id is zero.
Step 4: Write the OnHeaderClicked() function
Here’s where you decide what to do when the user clicks on a column header. The expected behaviour is to sort the list based on the values of the items in that column. In this function we have used the SortTextItems() function developed in a previous section. If any of the columns displays numeric or date values, then you would have to provide custom sorting for them.
void CMyListCtrl::OnHeaderClicked(NMHDR* pNMHDR, LRESULT* pResult)
{
HD_NOTIFY *phdn = (HD_NOTIFY *) pNMHDR;
if( phdn->iButton == 0 )
{
// User clicked on header using left mouse button
if( phdn->iItem == nSortedCol )
bSortAscending = !bSortAscending;
else
bSortAscending = TRUE;
nSortedCol = phdn->iItem;
SortTextItems( nSortedCol, bSortAscending );
}
*pResult = 0;
}
讓CListCtrl的SubItem也具有編輯功能:
要重載一個(gè)文本框,然后在LVN_BEGINLABELEDIT時(shí)改變文本框位置。
CInEdit m_InEdit;
if( ( GetStyle() & LVS_TYPEMASK ) == LVS_REPORT && ( m_nEditSubItem != 0 ) )
{
HWND hwndEdit;
CRect rtBound;
CString strText;
hwndEdit = (HWND)SendMessage( LVM_GETEDITCONTROL );
GetSubItemRect( pDispInfo->item.iItem, m_nEditSubItem, LVIR_LABEL, rtBound );
m_InEdit.SubclassWindow( hwndEdit );
m_InEdit.m_left = rtBound.left;
strText = GetItemText( pDispInfo->item.iItem, m_nEditSubItem );
m_InEdit.SetWindowText( strText );
}
void CInEdit::OnWindowPosChanging(WINDOWPOS FAR* lpwndpos)
{
CRect rtClient;
lpwndpos->x = m_left; // m_left在LVN_BEGINLABELEDIT中設(shè)置
CEdit::OnWindowPosChanging(lpwndpos);
// TODO: Add your message handler code here
}
預(yù)處理器(Preprocessor)
1. 用預(yù)處理指令#define 聲明一個(gè)常數(shù),用以表明1年中有多少秒(忽略閏年問(wèn)題)
#define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL
我在這想看到幾件事情:
1). #define 語(yǔ)法的基本知識(shí)(例如:不能以分號(hào)結(jié)束,括號(hào)的使用,等等)
2). 懂得預(yù)處理器將為你計(jì)算常數(shù)表達(dá)式的值,因此,直接寫(xiě)出你是如何計(jì)算一年中有多少秒而不是計(jì)算出實(shí)際的值,是更清晰而沒(méi)有代價(jià)的。
3). 意識(shí)到這個(gè)表達(dá)式將使一個(gè)16位機(jī)的整型數(shù)溢出-因此要用到長(zhǎng)整型符號(hào)L,告訴編譯器這個(gè)常數(shù)是的長(zhǎng)整型數(shù)。
4). 如果你在你的表達(dá)式中用到UL(表示無(wú)符號(hào)長(zhǎng)整型),那么你有了一個(gè)好的起點(diǎn)。記住,第一印象很重要。
2. 寫(xiě)一個(gè)“標(biāo)準(zhǔn)”宏MIN,這個(gè)宏輸入兩個(gè)參數(shù)并返回較小的一個(gè)。
#define MIN(A,B) ((A) <= (B) (A) : (B))
這個(gè)測(cè)試是為下面的目的而設(shè)的:
1). 標(biāo)識(shí)#define在宏中應(yīng)用的基本知識(shí)。這是很重要的,因?yàn)橹钡角度?inline)操作符變?yōu)闃?biāo)準(zhǔn)C的一部分,宏是方便產(chǎn)生嵌入代碼的唯一方法,對(duì)于嵌入式系統(tǒng)來(lái)說(shuō),為了能達(dá)到要求的性能,嵌入代碼經(jīng)常是必須的方法。
2). 三重條件操作符的知識(shí)。這個(gè)操作符存在C語(yǔ)言中的原因是它使得編譯器能產(chǎn)生比if-then-else更優(yōu)化的代碼,了解這個(gè)用法是很重要的。
3). 懂得在宏中小心地把參數(shù)用括號(hào)括起來(lái)
4). 我也用這個(gè)問(wèn)題開(kāi)始討論宏的副作用,例如:當(dāng)你寫(xiě)下面的代碼時(shí)會(huì)發(fā)生什么事?
least = MIN(*p++, b);
3. 預(yù)處理器標(biāo)識(shí)#error的目的是什么?
如果你不知道答案,請(qǐng)看參考文獻(xiàn)1。這問(wèn)題對(duì)區(qū)分一個(gè)正常的伙計(jì)和一個(gè)書(shū)呆子是很有用的。只有書(shū)呆子才會(huì)讀C語(yǔ)言課本的附錄去找出象這種
問(wèn)題的答案。當(dāng)然如果你不是在找一個(gè)書(shū)呆子,那么應(yīng)試者最好希望自己不要知道答案。
死循環(huán)(Infinite loops)4. 嵌入式系統(tǒng)中經(jīng)常要用到無(wú)限循環(huán),你怎么樣用C編寫(xiě)死循環(huán)呢?
這個(gè)問(wèn)題用幾個(gè)解決方案。我首選的方案是:
while(1) { }
一些程序員更喜歡如下方案:
for(;;) { }
這個(gè)實(shí)現(xiàn)方式讓我為難,因?yàn)檫@個(gè)語(yǔ)法沒(méi)有確切表達(dá)到底怎么回事。如果一個(gè)應(yīng)試者給出這個(gè)作為方案,我將用這個(gè)作為一個(gè)機(jī)會(huì)去探究他們這樣做的
基本原理。如果他們的基本答案是:“我被教著這樣做,但從沒(méi)有想到過(guò)為什么。”這會(huì)給我留下一個(gè)壞印象。
第三個(gè)方案是用 goto
Loop:
...
goto Loop;
應(yīng)試者如給出上面的方案,這說(shuō)明或者他是一個(gè)匯編語(yǔ)言程序員(這也許是好事)或者他是一個(gè)想進(jìn)入新領(lǐng)域的BASIC/FORTRAN程序員。
數(shù)據(jù)聲明(Data declarations) 5. 用變量a給出下面的定義
a) 一個(gè)整型數(shù)(An integer)
b) 一個(gè)指向整型數(shù)的指針(A pointer to an integer)
c) 一個(gè)指向指針的的指針,它指向的指針是指向一個(gè)整型數(shù)(A pointer to a pointer to an integer)
d) 一個(gè)有10個(gè)整型數(shù)的數(shù)組(An array of 10 integers)
e) 一個(gè)有10個(gè)指針的數(shù)組,該指針是指向一個(gè)整型數(shù)的(An array of 10 pointers to integers)
f) 一個(gè)指向有10個(gè)整型數(shù)數(shù)組的指針(A pointer to an array of 10 integers)
g) 一個(gè)指向函數(shù)的指針,該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)(A pointer to a function that takes an integer as an argument and returns an integer)
h) 一個(gè)有10個(gè)指針的數(shù)組,該指針指向一個(gè)函數(shù),該函數(shù)有一個(gè)整型參數(shù)并返回一個(gè)整型數(shù)( An array of ten pointers to functions that take an integer argument and return an integer )
答案是:
a) int a; // An integer
b) int *a; // A pointer to an integer
c) int **a; // A pointer to a pointer to an integer
d) int a[10]; // An array of 10 integers
e) int *a[10]; // An array of 10 pointers to integers
f) int (*a)[10]; // A pointer to an array of 10 integers
g) int (*a)(int); // A pointer to a function a that takes an integer argument and returns an integer
h) int (*a[10])(int); // An array of 10 pointers to functions that take an integer argument and return an integer
人們經(jīng)常聲稱這里有幾個(gè)問(wèn)題是那種要翻一下書(shū)才能回答的問(wèn)題,我同意這種說(shuō)法。當(dāng)我寫(xiě)這篇文章時(shí),為了確定語(yǔ)法的正確性,我的確查了一下書(shū)。
但是當(dāng)我被面試的時(shí)候,我期望被問(wèn)到這個(gè)問(wèn)題(或者相近的問(wèn)題)。因?yàn)樵诒幻嬖嚨倪@段時(shí)間里,我確定我知道這個(gè)問(wèn)題的答案。應(yīng)試者如果不知道
所有的答案(或至少大部分答案),那么也就沒(méi)有為這次面試做準(zhǔn)備,如果該面試者沒(méi)有為這次面試做準(zhǔn)備,那么他又能為什么出準(zhǔn)備呢?
Static6. 關(guān)鍵字static的作用是什么?
這個(gè)簡(jiǎn)單的問(wèn)題很少有人能回答完全。在C語(yǔ)言中,關(guān)鍵字static有三個(gè)明顯的作用:
1). 在函數(shù)體,一個(gè)被聲明為靜態(tài)的變量在這一函數(shù)被調(diào)用過(guò)程中維持其值不變。
2). 在模塊內(nèi)(但在函數(shù)體外),一個(gè)被聲明為靜態(tài)的變量可以被模塊內(nèi)所用函數(shù)訪問(wèn),但不能被模塊外其它函數(shù)訪問(wèn)。它是一個(gè)本地的全局變量。
3). 在模塊內(nèi),一個(gè)被聲明為靜態(tài)的函數(shù)只可被這一模塊內(nèi)的其它函數(shù)調(diào)用。那就是,這個(gè)函數(shù)被限制在聲明它的模塊的本地范圍內(nèi)使用。
大多數(shù)應(yīng)試者能正確回答第一部分,一部分能正確回答第二部分,同是很少的人能懂得第三部分。這是一個(gè)應(yīng)試者的嚴(yán)重的缺點(diǎn),因?yàn)樗@然不懂得本地化數(shù)據(jù)和代碼范圍的好處和重要性。
Const 7.關(guān)鍵字const是什么含意?
我只要一聽(tīng)到被面試者說(shuō):“const意味著常數(shù)”,我就知道我正在和一個(gè)業(yè)余者打交道。去年Dan Saks已經(jīng)在他的文章里完全概括了const的所有用法,因此ESP(譯者:Embedded Systems Programming)的每一位讀者應(yīng)該非常熟悉const能做什么和不能做什么.如果你從沒(méi)有讀到那篇文章,只要能說(shuō)出const意味著“只讀”就可以了。盡管這個(gè)答案不是完全的答案,但我接受它作為一個(gè)正確的答案。(如果你想知道更詳細(xì)的答案,仔細(xì)讀一下Saks的文章吧。)如果應(yīng)試者能正確回答這個(gè)問(wèn)題,我將問(wèn)他一個(gè)附加的問(wèn)題:下面的聲明都是什么意思?
const int a;
int const a;
const int *a;
int * const a;
int const * a const;
前兩個(gè)的作用是一樣,a是一個(gè)常整型數(shù)。第三個(gè)意味著a是一個(gè)指向常整型數(shù)的指針(也就是,整型數(shù)是不可修改的,但指針可以)。第四個(gè)意思a是一個(gè)指向整型數(shù)的常指針(也就是說(shuō),指針指向的整型數(shù)是可以修改的,但指針是不可修改的)。最后一個(gè)意味著a是一個(gè)指向常整型數(shù)的常指針(也就是說(shuō),指針指向的整型數(shù)是不可修改的,同時(shí)指針也是不可修改的)。如果應(yīng)試者能正確回答這些問(wèn)題,那么他就給我留下了一個(gè)好印象。順帶提一句,也許你可能會(huì)問(wèn),即使不用關(guān)鍵字const,也還是能很容易寫(xiě)出功能正確的程序,那么我為什么還要如此看重關(guān)鍵字const呢?我也如下的幾下理由:
1). 關(guān)鍵字const的作用是為給讀你代碼的人傳達(dá)非常有用的信息,實(shí)際上,聲明一個(gè)參數(shù)為常量是為了告訴了用戶這個(gè)參數(shù)的應(yīng)用目的。如果你曾花很多時(shí)間清理其它人留下的垃圾,你就會(huì)很快學(xué)會(huì)感謝這點(diǎn)多余的信息。(當(dāng)然,懂得用const的程序員很少會(huì)留下的垃圾讓別人來(lái)清理的。)
2). 通過(guò)給優(yōu)化器一些附加的信息,使用關(guān)鍵字const也許能產(chǎn)生更緊湊的代碼。
3). 合理地使用關(guān)鍵字const可以使編譯器很自然地保護(hù)那些不希望被改變的參數(shù),防止其被無(wú)意的代碼修改。簡(jiǎn)而言之,這樣可以減少bug的出現(xiàn)。
Volatile 8. 關(guān)鍵字volatile有什么含意 并給出三個(gè)不同的例子。
一個(gè)定義為volatile的變量是說(shuō)這變量可能會(huì)被意想不到地改變,這樣,編譯器就不會(huì)去假設(shè)這個(gè)變量的值了。精確地說(shuō)就是,優(yōu)化器在用到這個(gè)變量時(shí)必須每次都小心地重新讀取這個(gè)變量的值,而不是使用保存在寄存器里的備份。下面是volatile變量的幾個(gè)例子:
1). 并行設(shè)備的硬件寄存器(如:狀態(tài)寄存器)
2). 一個(gè)中斷服務(wù)子程序中會(huì)訪問(wèn)到的非自動(dòng)變量(Non-automatic variables)
3). 多線程應(yīng)用中被幾個(gè)任務(wù)共享的變量
回答不出這個(gè)問(wèn)題的人是不會(huì)被雇傭的。我認(rèn)為這是區(qū)分C程序員和嵌入式系統(tǒng)程序員的最基本的問(wèn)題。嵌入式系統(tǒng)程序員經(jīng)常同硬件、中斷、RTOS等等打交道,所用這些都要求volatile變量。不懂得volatile內(nèi)容將會(huì)帶來(lái)災(zāi)難。
假設(shè)被面試者正確地回答了這是問(wèn)題(嗯,懷疑這否會(huì)是這樣),我將稍微深究一下,看一下這家伙是不是直正懂得volatile完全的重要性。
1). 一個(gè)參數(shù)既可以是const還可以是volatile嗎?解釋為什么。
2). 一個(gè)指針可以是volatile 嗎?解釋為什么。
3). 下面的函數(shù)有什么錯(cuò)誤:
int square(volatile int *ptr)
{
return *ptr * *ptr;
}
下面是答案:
1). 是的。一個(gè)例子是只讀的狀態(tài)寄存器。它是volatile因?yàn)樗赡鼙灰庀氩坏降馗淖儭K莄onst因?yàn)槌绦虿粦?yīng)該試圖去修改它。
2). 是的。盡管這并不很常見(jiàn)。一個(gè)例子是當(dāng)一個(gè)中服務(wù)子程序修該一個(gè)指向一個(gè)buffer的指針時(shí)。
3). 這段代碼的有個(gè)惡作劇。這段代碼的目的是用來(lái)返指針*ptr指向值的平方,但是,由于*ptr指向一個(gè)volatile型參數(shù),編譯器將產(chǎn)生類似下面的代碼:
int square(volatile int *ptr)
{
int a,b;
a = *ptr;
b = *ptr;
return a * b;
}
由于*ptr的值可能被意想不到地該變,因此a和b可能是不同的。結(jié)果,這段代碼可能返不是你所期望的平方值!正確的代碼如下:
long square(volatile int *ptr)
{
int a;
a = *ptr;
return a * a;
}
位操作(Bit manipulation)9. 嵌入式系統(tǒng)總是要用戶對(duì)變量或寄存器進(jìn)行位操作。給定一個(gè)整型變量a,寫(xiě)兩段代碼,第一個(gè)設(shè)置a的bit 3,第二個(gè)清除a 的bit 3。在以上兩個(gè)操作中,要保持其它位不變。
對(duì)這個(gè)問(wèn)題有三種基本的反應(yīng)
1). 不知道如何下手。該被面者從沒(méi)做過(guò)任何嵌入式系統(tǒng)的工作。
2). 用bit fields。Bit fields是被扔到C語(yǔ)言死角的東西,它保證你的代碼在不同編譯器之間是不可移植的,同時(shí)也保證了的你的代碼是不可重用的。我最近不幸看到Infineon為其較復(fù)雜的通信芯片寫(xiě)的驅(qū)動(dòng)程序,它用到了bit fields因此完全對(duì)我無(wú)用,因?yàn)槲业木幾g器用其它的方式來(lái)實(shí)現(xiàn)bit fields的。從道德講:永遠(yuǎn)不要讓一個(gè)非嵌入式的家伙粘實(shí)際硬件的邊。
3). 用 #defines 和 bit masks 操作。這是一個(gè)有極高可移植性的方法,是應(yīng)該被用到的方法。最佳的解決方案如下:
#define BIT3 (0x1<<3)
static int a;
void set_bit3(void)
{
a |= BIT3;
}
void clear_bit3(void)
{
a &= ~BIT3;
}
一些人喜歡為設(shè)置和清除值而定義一個(gè)掩碼同時(shí)定義一些說(shuō)明常數(shù),這也是可以接受的。我希望看到幾個(gè)要點(diǎn):說(shuō)明常數(shù)、|=和&=~操作。
訪問(wèn)固定的內(nèi)存位置(Accessing fixed memory locations) 10. 嵌入式系統(tǒng)經(jīng)常具有要求程序員去訪問(wèn)某特定的內(nèi)存位置的特點(diǎn)。在某工程中,要求設(shè)置一絕對(duì)地址為0x67a9的整型變量的值為0xaa66。編譯器是一個(gè)純粹的ANSI編譯器。寫(xiě)代碼去完成這一任務(wù)。
這一問(wèn)題測(cè)試你是否知道為了訪問(wèn)一絕對(duì)地址把一個(gè)整型數(shù)強(qiáng)制轉(zhuǎn)換(typecast)為一指針是合法的。這一問(wèn)題的實(shí)現(xiàn)方式隨著個(gè)人風(fēng)格不同而不同。典型的類似代碼如下:
int *ptr;
ptr = (int *)0x67a9;
*ptr = 0xaa55;
一個(gè)較晦澀的方法是:
*(int * const)(0x67a9) = 0xaa55;
即使你的品味更接近第二種方案,但我建議你在面試時(shí)使用第一種方案。
中斷(Interrupts) 11. 中斷是嵌入式系統(tǒng)中重要的組成部分,這導(dǎo)致了很多編譯開(kāi)發(fā)商提供一種擴(kuò)展—讓標(biāo)準(zhǔn)C支持中斷。具代表事實(shí)是,產(chǎn)生了一個(gè)新的關(guān)鍵字__interrupt。下面的代碼就使用了__interrupt關(guān)鍵字去定義了一個(gè)中斷服務(wù)子程序(ISR),請(qǐng)?jiān)u論一下這段代碼的。
__interrupt double compute_area (double radius)
{
double area = PI * radius * radius;
printf(" Area = %f", area);
return area;
}
這個(gè)函數(shù)有太多的錯(cuò)誤了,以至讓人不知從何說(shuō)起了:
1). ISR 不能返回一個(gè)值。如果你不懂這個(gè),那么你不會(huì)被雇用的。
2). ISR 不能傳遞參數(shù)。如果你沒(méi)有看到這一點(diǎn),你被雇用的機(jī)會(huì)等同第一項(xiàng)。
3). 在許多的處理器/編譯器中,浮點(diǎn)一般都是不可重入的。有些處理器/編譯器需要讓額處的寄存器入棧,有些處理器/編譯器就是不允許在ISR中做浮點(diǎn)運(yùn)算。此外,ISR應(yīng)該是短而有效率的,在ISR中做浮點(diǎn)運(yùn)算是不明智的。
4). 與第三點(diǎn)一脈相承,printf()經(jīng)常有重入和性能上的問(wèn)題。如果你丟掉了第三和第四點(diǎn),我不會(huì)太為難你的。不用說(shuō),如果你能得到后兩點(diǎn),那么你的被雇用前景越來(lái)越光明了。
代碼例子(Code examples)12 . 下面的代碼輸出是什么,為什么?
void foo(void)
{
unsigned int a = 6;
int b = -20;
(a+b > 6) puts("> 6") : puts("<= 6");
}
這個(gè)問(wèn)題測(cè)試你是否懂得C語(yǔ)言中的整數(shù)自動(dòng)轉(zhuǎn)換原則,我發(fā)現(xiàn)有些開(kāi)發(fā)者懂得極少這些東西。不管如何,這無(wú)符號(hào)整型問(wèn)題的答案是輸出是“>6”。原因是當(dāng)表達(dá)式中存在有符號(hào)類型和無(wú)符號(hào)類型時(shí)所有的操作數(shù)都自動(dòng)轉(zhuǎn)換為無(wú)符號(hào)類型。 因此-20變成了一個(gè)非常大的正整數(shù),所以該表達(dá)式計(jì)算出的結(jié)果大于6。這一點(diǎn)對(duì)于應(yīng)當(dāng)頻繁用到無(wú)符號(hào)數(shù)據(jù)類型的嵌入式系統(tǒng)來(lái)說(shuō)是豐常重要的。如果你答錯(cuò)了這個(gè)問(wèn)題,你也就到了得不到這份工作的邊緣。
13. 評(píng)價(jià)下面的代碼片斷:
unsigned int zero = 0;
unsigned int compzero = 0xFFFF;
/*1's complement of zero */
對(duì)于一個(gè)int型不是16位的處理器為說(shuō),上面的代碼是不正確的。應(yīng)編寫(xiě)如下:
unsigned int compzero = ~0;
這一問(wèn)題真正能揭露出應(yīng)試者是否懂得處理器字長(zhǎng)的重要性。在我的經(jīng)驗(yàn)里,好的嵌入式程序員非常準(zhǔn)確地明白硬件的細(xì)節(jié)和它的局限,然而PC機(jī)程序往往把硬件作為一個(gè)無(wú)法避免的煩惱。
到了這個(gè)階段,應(yīng)試者或者完全垂頭喪氣了或者信心滿滿志在必得。如果顯然應(yīng)試者不是很好,那么這個(gè)測(cè)試就在這里結(jié)束了。但如果顯然應(yīng)試者做得不錯(cuò),那么我就扔出下面的追加問(wèn)題,這些問(wèn)題是比較難的,我想僅僅非常優(yōu)秀的應(yīng)試者能做得不錯(cuò)。提出這些問(wèn)題,我希望更多看到應(yīng)試者應(yīng)付問(wèn)題的方法,而不是答案。不管如何,你就當(dāng)是這個(gè)娛樂(lè)吧…
動(dòng)態(tài)內(nèi)存分配(Dynamic memory allocation)14. 盡管不像非嵌入式計(jì)算機(jī)那么常見(jiàn),嵌入式系統(tǒng)還是有從堆(heap)中動(dòng)態(tài)分配內(nèi)存的過(guò)程的。那么嵌入式系統(tǒng)中,動(dòng)態(tài)分配內(nèi)存可能發(fā)生的問(wèn)題是什么?
這里,我期望應(yīng)試者能提到內(nèi)存碎片,碎片收集的問(wèn)題,變量的持行時(shí)間等等。這個(gè)主題已經(jīng)在ESP雜志中被廣泛地討論過(guò)了(主要是 P.J. Plauger, 他的解釋遠(yuǎn)遠(yuǎn)超過(guò)我這里能提到的任何解釋),所有回過(guò)頭看一下這些雜志吧!讓?xiě)?yīng)試者進(jìn)入一種虛假的安全感覺(jué)后,我拿出這么一個(gè)小節(jié)目:下面的代碼片段的輸出是什么,為什么?
char *ptr;
if ((ptr = (char *)malloc(0)) == NULL)
puts("Got a null pointer");
else
puts("Got a valid pointer");
這是一個(gè)有趣的問(wèn)題。最近在我的一個(gè)同事不經(jīng)意把0值傳給了函數(shù)malloc,得到了一個(gè)合法的指針之后,我才想到這個(gè)問(wèn)題。這就是上面的代碼,該代碼的輸出是“Got a valid pointer”。我用這個(gè)來(lái)開(kāi)始討論這樣的一問(wèn)題,看看被面試者是否想到庫(kù)例程這樣做是正確。得到正確的答案固然重要,但解決問(wèn)題的方法和你做決定的基本原理更重要些。
Typedef
15. Typedef 在C語(yǔ)言中頻繁用以聲明一個(gè)已經(jīng)存在的數(shù)據(jù)類型的同義字。也可以用預(yù)處理器做類似的事。例如,思考一下下面的例子:
#define dPS struct s *
typedef struct s * tPS;
以上兩種情況的意圖都是要定義dPS 和 tPS 作為一個(gè)指向結(jié)構(gòu)s指針。哪種方法更好呢?(如果有的話)為什么?
這是一個(gè)非常微妙的問(wèn)題,任何人答對(duì)這個(gè)問(wèn)題(正當(dāng)?shù)脑颍┦菓?yīng)當(dāng)被恭喜的。答案是:typedef更好。思考下面的例子:
dPS p1,p2;
tPS p3,p4;
第一個(gè)擴(kuò)展為
struct s * p1, p2;
上面的代碼定義p1為一個(gè)指向結(jié)構(gòu)的指,p2為一個(gè)實(shí)際的結(jié)構(gòu),這也許不是你想要的。第二個(gè)例子正確地定義了p3 和p4 兩個(gè)指針。
16. C語(yǔ)言同意一些令人震驚的結(jié)構(gòu),下面的結(jié)構(gòu)是合法的嗎,如果是它做些什么?
int a = 5, b = 7, c;
c = a+++b;
這個(gè)問(wèn)題將做為這個(gè)測(cè)驗(yàn)的一個(gè)愉快的結(jié)尾。不管你相不相信,上面的例子是完全合乎語(yǔ)法的。問(wèn)題是編譯器如何處理它?水平不高的編譯作者實(shí)際上會(huì)爭(zhēng)論這個(gè)問(wèn)題,根據(jù)最處理原則,編譯器應(yīng)當(dāng)能處理盡可能所有合法的用法。因此,上面的代碼被處理成:
c = a++ + b;
因此, 這段代碼持行后a = 6, b = 7, c = 12。
轉(zhuǎn)自: http://hi.baidu.com/jenfmo/blog/item/b000c50a7acb8e3ab1351dfd.html
前言
ActiveX控件是一種實(shí)現(xiàn)了一系列特定接口而使其在使用和外觀上更象一個(gè)控件的COM組件。ActiveX控件這種技術(shù)涉及到了幾乎所有的COM和OLE的技術(shù)精華,如可鏈接對(duì)象、統(tǒng)一數(shù)據(jù)傳輸、OLE文檔、屬性頁(yè)、永久存儲(chǔ)以及OLE自動(dòng)化等。
ActiveX控件作為基本的界面單元,必須擁有自己的屬性和方法以適合不同特點(diǎn)的程序和向包容器程序提供功能服務(wù),其屬性和方法均由自動(dòng)化服務(wù)的IDispatch接口來(lái)支持。除了屬性和方法外,ActiveX控件還具有區(qū)別于自動(dòng)化服務(wù)的一種特性--事件。事件指的是從控件發(fā)送給其包容程序的一種通知。與窗口控件通過(guò)發(fā)送消息通知其擁有者類似,ActiveX控件是通過(guò)觸發(fā)事件來(lái)通知其包容器的。事件的觸發(fā)通常是通過(guò)控件包容器提供的IDispatch接口來(lái)調(diào)用自動(dòng)化對(duì)象的方法來(lái)實(shí)現(xiàn)的。在設(shè)計(jì)ActiveX控件時(shí)就應(yīng)當(dāng)考慮控件可能會(huì)發(fā)生哪些事件以及包容器程序?qū)?huì)對(duì)其中的哪些事件感興趣并將這些事件包含進(jìn)來(lái)。與自動(dòng)化服務(wù)不同,ActiveX控件的方法、屬性和事件均有自定義(custom)和庫(kù)存(stock)兩種不同的類型。自定義的方法和屬性也就是是普通的自動(dòng)化方法和屬性,自定義事件則是自己選取名字和Dispatch ID的事件。而所謂的庫(kù)存方法、屬性和事件則是使用了ActiveX控件規(guī)定了名字和Dispatch ID的"標(biāo)準(zhǔn)"方法、屬性和事件。
ActiveX控件可以使COM組件從外觀和使用上能與普通的窗口控件一樣,而且還提供了類似于設(shè)置Windows標(biāo)準(zhǔn)控件屬性的屬性頁(yè),使其能夠在包容器程序的設(shè)計(jì)階段對(duì)ActiveX控件的屬性進(jìn)行可視化設(shè)置。ActiveX控件提供的這些功能使得對(duì)其的使用將是非常方便的。本文下面即以MFC為工具對(duì)ActiveX控件的開(kāi)發(fā)進(jìn)行介紹。
Description: 前言 本文全面的介紹了RSA算法的概念、原理、證明和實(shí)現(xiàn)。我在寫(xiě)作本文之前在網(wǎng)上查閱過(guò)相關(guān)資料,可這些資料不是含糊其辭就是滿篇謬誤。所以我力求用通俗易懂的文字將算法深入剖析,用最嚴(yán)謹(jǐn)?shù)牟襟E進(jìn)行論相關(guān)的各項(xiàng)算法,以降低文章的閱讀難度。讀者只要學(xué)過(guò)初中代數(shù)就可以理解全文,我衷心希望更多讀者能認(rèn)識(shí)到加密算法其實(shí)并不難。文中的算法均為偽代碼,由于偽代碼沒(méi)有辦法進(jìn)行測(cè)試,再加上我個(gè)人數(shù)學(xué)功底比較薄弱,所以錯(cuò)漏之處在所難免,還請(qǐng)各位老師給予指教。質(zhì)疑或指正請(qǐng)發(fā)送電子郵件到fireseed1949@hotmail.com,我會(huì)認(rèn)真閱讀并回復(fù)的!感謝北航數(shù)學(xué)系(畢業(yè))李楨老師、西工大計(jì)算機(jī)系(畢業(yè))張小寧老師在數(shù)學(xué)上對(duì)我的指點(diǎn)。另注:文中mod就是求余的符號(hào),X mod Y表示X除以Y所得的余數(shù)。 ·概述 RSA算法是世界上第一個(gè)既能用于數(shù)據(jù)加密也能用于數(shù)字簽名的非對(duì)稱性加密算法。它易于理解和操作,所以流行甚廣。算法的名字以發(fā)明者的名字命名,他們是:Ron Rivest,Adi Shamir 和Leonard Adleman。雖然RSA的安全性一直未能得到理論上的證實(shí),但它經(jīng)歷了各種攻擊,至今未被完全攻破。為了讓讀者更容易的理解RSA加密,先大概講述一下信息加密技術(shù)的相關(guān)概念和原理。我們對(duì)于在數(shù)字媒體上進(jìn)行交換的數(shù)據(jù)進(jìn)行加密的方法稱為信息交換加密技術(shù),它分為兩類,即對(duì)稱加密和非對(duì)稱加密。在對(duì)稱加密技術(shù)中,對(duì)信息的加密和解密都使用相同的鑰,也就是說(shuō)一把鑰匙開(kāi)一把鎖。這種加密方法可簡(jiǎn)化加密處理過(guò)程,信息交換雙方都不必彼此研究和交換專用的加密算法。如果在交換階段私有密鑰未曾泄露,那么機(jī)密性和報(bào)文完整性就可以得以保證。對(duì)稱加密技術(shù)也存在一些不足,如果交換一方有N個(gè)交換對(duì)象,那么他就要維護(hù)N個(gè)私有密鑰,對(duì)稱加密存在的另一個(gè)問(wèn)題是雙方共享一把私有密鑰,交換雙方的任何信息都是通過(guò)這把密鑰加密后傳送給對(duì)方的。如三重DES是DES(數(shù)據(jù)加密標(biāo)準(zhǔn))的一種變形,這種方法使用兩個(gè)獨(dú)立的56為密鑰對(duì)信息進(jìn)行3次加密,從而使有效密鑰長(zhǎng)度達(dá)到112位。在非對(duì)稱加密(或稱公開(kāi)密鑰加密)體系中,密鑰被分解為一對(duì),即公開(kāi)密鑰(公鑰)和私有密鑰(私鑰)。這對(duì)密鑰中任何一把都可以作為公開(kāi)密鑰,通過(guò)非保密方式向他人公開(kāi),而另一把作為私有密鑰,加以妥善保存。公開(kāi)密鑰用于加密,私有密鑰用于解密,私有密鑰只能由生成密鑰的交換方掌握,公開(kāi)密鑰可廣泛公布,但它只對(duì)應(yīng)于生成密鑰的交換方。非對(duì)稱加密方式可以使通信雙方無(wú)須事先交換密鑰就可以建立安全通信,廣泛應(yīng)用于身份認(rèn)證、數(shù)字簽名等信息交換領(lǐng)域。非對(duì)稱加密體系一般是建立在某些已知的數(shù)學(xué)難題之上,是計(jì)算機(jī)復(fù)雜性理論發(fā)展的必然結(jié)果。最具有代表性是RSA公鑰密碼體制。在RSA算法中,我們先要獲得兩個(gè)不同的質(zhì)數(shù)P和Q做為算法因子,再找出一個(gè)正整數(shù)E,使得E與 ( P - 1 ) * ( Q - 1 ) 的值互質(zhì),這個(gè)E就是私鑰。找到一個(gè)整數(shù)D,使得( E * D ) mod ( ( P - 1 ) * ( Q - 1 ) ) = 1成立,D就是公鑰1。設(shè)N為P和Q的乘積,N則為公鑰2。加密時(shí)先將明文轉(zhuǎn)換為一個(gè)或一組小于N的整數(shù)I,并計(jì)算ID mod N的值M,M就密文。解密時(shí)將密文ME mod N,也就是M的E次方再除以N所得的余數(shù)就是明文。因?yàn)樗借€E與( P - 1 ) * ( Q - 1 )互質(zhì),而公鑰D使( E * D ) mod ( ( P - 1 ) * ( Q - 1 ) ) = 1成立。破解者可以得到D和N,如果想要得到E,必須得出( P - 1 ) * ( Q - 1 ),因而必須先對(duì)N進(jìn)行因數(shù)分解。如果N很大那么因數(shù)分解就會(huì)非常困難,所以要提高加密強(qiáng)度P和Q的數(shù)值大小起著決定性的因素。一般來(lái)講當(dāng)P和Q都大于2128時(shí),按照目前的機(jī)算機(jī)處理速度破解基本已經(jīng)不大可能了。 ·證明下面將會(huì)開(kāi)始討論RSA算法的原理及其算法證明。如果您只關(guān)心RSA算法的實(shí)現(xiàn),則可以略過(guò)這一步。我把每一個(gè)有用的定理都用粗標(biāo)標(biāo)記了,對(duì)于數(shù)學(xué)不很在行的朋友可以只了解一下相關(guān)定理的說(shuō)明而不需要驗(yàn)證求證過(guò)程了。一、 費(fèi)馬小定理[1]的轉(zhuǎn)化費(fèi)馬小定理:有N為任意正整數(shù),P為素?cái)?shù),且N不能被P整除,則有: NP mod P = N 費(fèi)馬小定理可變形為: NP - N mod P = 0 ( N ( NP - 1 - 1 ) ) mod P = 0 因?yàn)?( N ( NP - 1 - 1 ) ) mod N = 0 所以N和P的公倍數(shù)為: N ( NP - 1 - 1 ) (1)又因?yàn)镹與P互質(zhì),而互質(zhì)數(shù)的最小公倍數(shù)為它們的乘積,所以一定存在正整數(shù)M使得:N ( NP - 1 - 1 ) = MNP成立。并化簡(jiǎn)為: NP - 1 - 1 = MP ( NP - 1 - 1 ) mod P = 0 可以變形為: NP - 1 mod P = 1 (2)(2)就是費(fèi)馬小定理的轉(zhuǎn)化定理,為方便敘述,下文簡(jiǎn)稱為定理一。小提示,可能很多人認(rèn)為費(fèi)馬小定理本來(lái)就是(2),實(shí)際上不是這樣,因?yàn)橘M(fèi)馬小定理的轉(zhuǎn)化非常容易,而轉(zhuǎn)化定理又是一個(gè)無(wú)論在數(shù)學(xué)上還是計(jì)算機(jī)程序上都很常用的公式,所以人們就普遍認(rèn)為(2)就是費(fèi)馬小定理了。二、 積模分解公式有X、Y和Z三個(gè)正整數(shù),且X * Y大于Z,則有: ( X * Y ) mod Z = ( ( X mod Z ) * ( Y mod Z ) ) mod Z 證明如下當(dāng)X和Y都比Z大時(shí),可以將X和Y表示為: X = ZI + A (1) Y = ZJ + B (2)將(1)和(2)代入( X * Y ) mod Z得: ( ( ZI + A )( ZJ + B ) ) mod Z ( Z( ZIJ + IA + IB ) + AB ) mod Z (3)因?yàn)閆( ZIJ + IA + IB )是Z的整數(shù)倍,所以(3)式可化簡(jiǎn)為: AB mod Z 因?yàn)锳和B實(shí)際上是X和Y分別除以Z的余數(shù),所以有: ( X * Y ) mod Z = ( ( X mod Z ) * ( Y mod Z ) ) mod Z成立。當(dāng)X比Z大而Y比Z小時(shí) X = ZI + A 代入( X * Y ) mod Z得: ( ZIY + AY ) mod Z AY mod Z 因?yàn)锳 = X mod Z, 又因?yàn)閅 mod Z = Y,所以有: ( X * Y ) mod Z = ( ( X mod Z ) * ( Y mod Z ) ) mod Z成立。同理,當(dāng)X比Z小而Y比Z大時(shí),上式也成立。當(dāng)X和Y都比Z小時(shí),X = X mod Z,Y = Y mod Z所以有: ( X * Y ) mod Z = ( ( X mod Z ) * ( Y mod Z ) ) mod Z成立。積模分解公式成立。三、 定理二有P和Q兩個(gè)互質(zhì)數(shù),如果有X mod P = 0,X mod Q = 0,則有:X mod PQ = 0 證明:因?yàn)镻和Q互質(zhì),所以它們的公倍數(shù)為KPQ(K為整數(shù)),最小公倍數(shù)為PQ。又因?yàn)閄為P和Q的公倍數(shù),所以X / PQ = K,所以X mod PQ = 0。四、 定理三有P和Q兩個(gè)互質(zhì)數(shù),設(shè)有整數(shù)X和Y滿足Y mod P = X,Y mod Q = X,則有:Y mod PQ = X 證明: X = Y mod P 可以表示為: Y = X + kP Y - X = kP 即Y - X可以被P整除,同理Y - X可以被Q整除。又因?yàn)镻、Q互質(zhì),根據(jù)定理二可得: ( Y - X ) mod PQ = 0 即 Y mod PQ = X 五、 定理三的逆定理有P和Q兩個(gè)互質(zhì)數(shù),設(shè)有整數(shù)X和Y滿足Y mod PQ = X ,則有:Y mod P = X,Y mod Q = X 證明: Y mod PQ = X 可以表示為: ( Y – X ) mod PQ = 0 顯然 ( Y – X ) mod P = 0且 ( Y – X ) mod Q = 0 所以原命題成立。六、 RSA定理若P和Q是兩個(gè)相異質(zhì)數(shù),另有正整數(shù)R和M,其中M的值與( P - 1 )( Q - 1 )的值互質(zhì),并使得( RM ) mod ( P - 1 )( Q - 1 ) = 1。有正整數(shù)A,且A
PQ,且A不是P的倍數(shù)也不是Q的倍數(shù)時(shí),(2)可變形為: B = ( AAK ( P - 1 )( Q - 1 ) ) mod PQ 根據(jù)積模分解公式可變形為: B = ( ( A mod PQ )( AK ( P - 1 )( Q - 1 ) mod PQ ) ) mod PQ (3)根據(jù)定理三的逆定理: AK ( P - 1 )( Q - 1 ) mod PQ = ( AK ( P - 1 ) ) ( Q - 1 ) mod Q 根據(jù)費(fèi)馬小定理可得: ( AK ( P - 1 ) ) ( Q - 1 ) mod Q = 1,則 AK ( P - 1 )( Q - 1 ) mod PQ = 1 故( 3 )可轉(zhuǎn)化為: B = ( A mod PQ ) mod PQ 因?yàn)锳
PQ,且A不是P的倍數(shù)而是Q的倍數(shù)時(shí),A可表示為A = NQ,N為一小于A的整數(shù)。那么(2)式可變形為: B = ( NQ )K ( P - 1 )( Q - 1 ) + 1 mod PQ B = ( NK ( P - 1 )( Q - 1 ) + 1 )( QK ( P - 1 )( Q - 1 ) + 1 ) mod PQ 把Q作為公因子提出來(lái),得: B = ( ( NNK ( P - 1 )( Q - 1 ) ) ( QK ( P - 1 )( Q - 1 ) mod P ) ) Q 用積模分解公式進(jìn)行分解,得: B = ( ( NNK ( P - 1 )( Q - 1 ) mod P )( QK ( P - 1 )( Q - 1 ) mod P ) mod P ) Q 跟據(jù)定理四,NK ( P - 1 )( Q - 1 )和QK ( P - 1 )( Q - 1 )的值都為1,所以有: B = ( ( ( N mod P ) mod P ) mod P ) Q B = NQ mod PQ mod PQ mod PQ B = A mod PQ mod PQ mod PQ 因?yàn)锳
1 E := E / 2,余數(shù)存入M IF M = 1 K := R * K END IF R := R * R NEXT R := R * K 再回到我們剛才討論的冪模運(yùn)算。事實(shí)上在(1)式中,我們需要求出的就是( N mod D )R的值,那么只要令上面?zhèn)未a中參量N的值為N mod D,并對(duì)結(jié)果R求R mod D就可以了,下面是基于上面求乘方算法的冪模運(yùn)算的偽代碼。算法二:計(jì)算N的E次方再取D的模,令R為計(jì)算結(jié)果。 R := N mod D R := R ^ E ;調(diào)用算法一 R := R % D 如果再利用上文過(guò)程中提到積模分解公式對(duì)算法做進(jìn)一步優(yōu)化,直接把取余的運(yùn)算代入到乘方中,就成為了著名的蒙格馬利快速冪模運(yùn)算法,偽代碼如下。算法三:蒙格馬利法計(jì)算N的E次方再取D的模,令R為計(jì)算結(jié)果。 R := 1 A := N B := E WHILE B != 0 IF B & 1 ;判斷是否為奇數(shù) B := B - 1 R := R * A X := X % D ELSE B := B / 2 A := A * A A := A % D END IF NEXT 蒙格馬利快速冪模運(yùn)算,是目前世界上效率最高的冪模運(yùn)算,很多硬件芯片在處理類似算法時(shí)都采用的這種方法。 ·尋找大素?cái)?shù)為了有效防止破解,必要須找到兩個(gè)很大的素?cái)?shù)作為算法因子。而尋找大素?cái)?shù),是數(shù)學(xué)家們一個(gè)永恒的話題。素?cái)?shù)的定義是只能被自己和1整除的自然數(shù),按照常規(guī)的理解,使用計(jì)算機(jī)對(duì)一個(gè)很大的數(shù)進(jìn)行素?cái)?shù)測(cè)試時(shí),需要遍歷所有小于它且大于1的自然數(shù),并逐個(gè)判斷是否能被該數(shù)整除。這個(gè)過(guò)程對(duì)于非常大的素?cái)?shù)而言是非常緩慢的。但是根據(jù)費(fèi)馬小定理,我們可以設(shè)計(jì)一種算法來(lái)快速測(cè)試素?cái)?shù)。當(dāng)A和Q互質(zhì)時(shí),有:AQ - 1 mod Q = 1,那么,我們可以通過(guò)判斷AQ - 1 mod Q的值是否等于1對(duì)Q進(jìn)行素?cái)?shù)測(cè)試。如果取了很多個(gè)A,Q仍未測(cè)試失敗,那么則認(rèn)為Q是素?cái)?shù)。當(dāng)然,測(cè)試次數(shù)越多越準(zhǔn)確,但一般來(lái)講50次就足夠了。另外,預(yù)先用常歸算法構(gòu)造一個(gè)包括500個(gè)素?cái)?shù)的數(shù)組,先對(duì)Q進(jìn)行整除測(cè)試,將會(huì)大大提高通過(guò)率,方法如下:算法四:費(fèi)馬定理測(cè)試可能素?cái)?shù)P C := 500 ;素?cái)?shù)表大小 S[ 0 TO C ] ;素?cái)?shù)表 B := P - 1 T := 50 ;表示進(jìn)行測(cè)試的次數(shù) A := 0 FOR I := 0 TO C ;進(jìn)行素?cái)?shù)表初步測(cè)試 IF P mod S[I] = 0 RETURN FAILE END IF IF P 1 RETURN FAILE END IF NEXT I RETURN PASS 這個(gè)算法看起來(lái)很完美,但實(shí)際上從一開(kāi)始它就犯了一個(gè)很大的錯(cuò),那就是對(duì)于任意與Q互質(zhì)的A都有AQ - 1 mod Q = 1,這是素?cái)?shù)的性質(zhì),是素?cái)?shù)成立的一個(gè)必要條件,但不是充分條件!讓我們來(lái)看一下29341這個(gè)數(shù),它等于13 * 37 * 61,但任何與它互質(zhì)的A都有A29341 - 1 mod 29341 = 1成立。這種數(shù)字還有不少,數(shù)學(xué)上把它們稱為卡爾麥克數(shù),現(xiàn)在數(shù)學(xué)家們已經(jīng)找到所有1016以內(nèi)的卡爾麥克數(shù),最大的一個(gè)是9585921133193329。我們必須尋找更為有效的測(cè)試方法。數(shù)學(xué)家們通過(guò)對(duì)費(fèi)馬小定理的研究,并加以擴(kuò)展,總結(jié)出了多種快速有效的素?cái)?shù)測(cè)試方法,目前最快的算法是拉賓米勒測(cè)試算法,其過(guò)程如下:首先確定N是否為奇數(shù),不是奇數(shù)的判斷失敗。選擇T個(gè)隨機(jī)整數(shù)A,并且有 0 50那么測(cè)試失誤的機(jī)率就會(huì)小于10-30,這對(duì)于目前的計(jì)算機(jī)硬件來(lái)說(shuō)已經(jīng)足夠證明N就是素?cái)?shù)了。下面是偽代碼。算法五:拉賓米勒測(cè)試法測(cè)試P是否為素?cái)?shù)。 C := 500 ;素?cái)?shù)表大小 S[ 0 TO C ] ;素?cái)?shù)表 B := P - 1 T := 50 ;表示進(jìn)行測(cè)試的次數(shù) A := 0 ;用來(lái)測(cè)試通過(guò)的隨機(jī)整數(shù) FOR I := 0 TO C ;進(jìn)行素?cái)?shù)表初步測(cè)試 IF P mod S[I] = 0 RETURN FAILE END IF IF P > 1 R := R + 1 ;計(jì)算R NEXT X := 0 Y := 0 FOR I := 0 TO T A := S[ RAND() mod C ] ;先進(jìn)行費(fèi)馬測(cè)試 IF A ^ ( P - 1 ) mod P <> 1 RETURN FAILE END IF X := A Y := A ^ ( M * R * 2 ) WHILE X <= Y IF X ^ M mod P = 1 BREAK END IF X := X ^ 2 NEXT IF X > Y RETURN FAILE END IF NEXT RETURN PASS ·二元一次不定方程在算法概述的章節(jié)里我們?cè)?jīng)討論過(guò)公鑰1的求法:找一個(gè)數(shù)D,使得( E * D ) mod ( ( P - 1 ) * ( Q - 1 ) ) = 1成立。為了求D,我們先對(duì)這個(gè)方程變形。實(shí)際上這個(gè)方程可以看做AX mod B = 1,即: AX = BY + 1,Y為一整數(shù)。 AX - BY = 1 這就是一個(gè)二元一次不定方程,有已知數(shù)A、B,未知數(shù)X、Y。我們現(xiàn)在需要求的是X,那么就是求這個(gè)方程對(duì)于X的最小整數(shù)解。由于方程有兩個(gè)未知數(shù),所以必須化簡(jiǎn)方程,使得一個(gè)未知數(shù)的系數(shù)為0時(shí)才能得解。設(shè)B > A時(shí)有: AX - BY = 1 那么可以認(rèn)為B = AN + M,則有: AX - ( AN + M )Y = 1 AX - ANY - MY = 1 A( X - NY ) - MY = 1 實(shí)際上M就是B mod A的值,設(shè)X’ = X - NY,B’ = B mod A則有AX’ - B’Y = 1,且A > M成立。接著可以用同樣的方法來(lái)化簡(jiǎn)A,最終必能將一個(gè)系數(shù)化為0。此時(shí)求出另一個(gè)未知數(shù)的解,再按逆序代入上一步的方程,求出另一個(gè)未知數(shù)的解,再代入上一步的方程,一直遞推的第一個(gè)方程,最終即可獲得X和Y的最小整數(shù)解。因?yàn)槊恳徊竭f推的方程的余數(shù)相同,所以我們稱這些方程為“一次同余式”。這個(gè)算法被稱為歐幾里德擴(kuò)展算法,而歐幾里德算法其實(shí)就是求公因式的輾轉(zhuǎn)相除法,大多數(shù)朋友在中學(xué)時(shí)就學(xué)過(guò)了,但是我們下面會(huì)用到,所以我這里簡(jiǎn)單的用偽代碼來(lái)描述一下歐幾里德算法。算法六:求A和B兩相異自然數(shù)的最大公因數(shù),另R為結(jié)果。 IF A P。并且有正整數(shù)D使ND mod P = 1成立,求D。 IF N和P的最大公因數(shù) <> 1 ;調(diào)用算法六 RETURN FAILE END IF LT := 1 ;左上 RT := N mod P ;右上 LD := 0 ;左下 RD := P ;右下 X := 0 ;中間變量 WHILE RT <> 1 X := RD / RT RD := RD % RT IF RD = 0 RD := RT LD := ( X - 1 ) * LT + LD ELSE LD := X * LT + 1 END IF X := RT / RD RT := RT % RD IF RT = 0 RT := RD LT := ( X - 1 ) * LD + LT ELSE LT := X * LD + 1 END IF NEXT D := LT ·結(jié)語(yǔ)到現(xiàn)在,RSA算法中所涉及到的所有算法我們都已經(jīng)討論過(guò)了。實(shí)際還有一個(gè)運(yùn)算,就是私鑰的獲得辦法:計(jì)算得到與( P - 1 ) * ( Q - 1 )的值互質(zhì)的整數(shù)E。我情愿不把它稱之為算法,因?yàn)橹恍枰粋€(gè)循環(huán)和一個(gè)判斷就可以完成,所以這里也就沒(méi)有必要對(duì)它多加論述了。 ·附錄費(fèi)馬小定理的證明:引理1:設(shè)M,A的最大公約數(shù)( M,A ) = 1,且M整除AB,即M mod AB = 0,則M mod B = 0。引理2:設(shè)P是素?cái)?shù),
表示組合數(shù),即從P個(gè)數(shù)中選出J個(gè)數(shù)的組合種數(shù),且1 ≤ J ≤ P - 1,則P mod
。證明:已知組合數(shù)
= P! / ( J! * ( P - J )! )是整數(shù),即J! * ( P - J )! mod P! = 0。由于P是素?cái)?shù),所以對(duì)任意1 ≤ I ≤ P-1有( P,I ) = 1。因此由引理1有( P,j! * ( P - J )! ) = 1,1 ≤ J ≤ P - 1。進(jìn)而由引理1推出:當(dāng)1 ≤ J ≤ P - 1時(shí)J! * ( P - J )! mod ( P - 1 )! = 0,得證。
1003 1004 1005 1009 1016 1019 1020 1021 1023 1040 1042 1049 1058 1070 1071 1076 1084 1097 1108 1128 1130 1134 1157 1161 1163 1164 1170 1176 1177 1178 1196 1197 1200 1201 1202 1212 1215 1219 1225 1228 1229 1231 1234 1235 1236 1239 1248 1249 1250 1257 1262 1263 1266 1276 1279 1280 1282 1283 1284 1286 1290 1297 1303 1321 1326 1328 1337 1395 1405 1406 1412 1420 1425 1491 1517 1536 1555 1562 1563 1597 1715 1720 1785 1846 1847 1848 1849 1850 2028 2029 2031 2032 2034 2039 2040 2043 2044 2047 2048 2049 2052 2053 2057 2064 2067 2071 2072 2074 2076 2079 2080 2083 2085 2086 2087 2088 2090 2091 2092 2097 2098 2100 2101 2106 2107 2109 2111 2114 2115 2117 2123 2124 2130 2131 2132 2134 2135 2139 2140 2147
HDOJ過(guò)兩百,先拿這些題下手
說(shuō)實(shí)話,我剛開(kāi)始看見(jiàn)Tab Control的時(shí)候,覺(jué)得很簡(jiǎn)單。哪知道用了一下,才發(fā)現(xiàn)自己錯(cuò)了。
要用好它,還是需要一些技巧的。經(jīng)過(guò)網(wǎng)上搜索資料,以及我自己的摸索,把一些要點(diǎn)記錄在這里。
Tab Control的運(yùn)行效果有點(diǎn)像Property Sheet,但兩者還是有一些區(qū)別。我的理解就是Property Sheet主要用在對(duì)話框中,對(duì)數(shù)據(jù)進(jìn)行進(jìn)行分類管理。而Tab Control使用范圍更廣一些,既可以用在對(duì)話框,也可以用在視圖中,除了可以管理配置數(shù)據(jù)外,還可以對(duì)軟件的組織進(jìn)行規(guī)劃,比如可以通過(guò)它來(lái)切換不同的視圖等等。
當(dāng)然這不是沒(méi)有代價(jià)的,Tab Control的編程就比Property Sheet的復(fù)雜很多。
我最初有點(diǎn)搞不懂,如何在Tab Control中使用不同的Page,就象Property Page一樣,Tab Control并沒(méi)有提供便利的機(jī)制讓你輕松做到這一點(diǎn)。還好,VC是最棒的,撒花~通過(guò)變通的方法還是可以做到這一點(diǎn)。
不羅嗦了,上代碼。
假如我現(xiàn)在有個(gè)SDI程序,View是Form View,想在上面放個(gè)Tab Control,包含兩個(gè)Page。現(xiàn)在讓我們來(lái)看看應(yīng)該怎樣處理。
首先當(dāng)然要增加一個(gè)Tab Control資源,然后利用Class Wizard,在View中增加一個(gè)Control變量。
接著建立兩個(gè)對(duì)話框資源,別忘了把Style改為Child,Border改為None。然后就可以在上面加其他控件了。
接著利用Class Wizard,分別為這兩個(gè)對(duì)話框建立兩個(gè)類,比如CPage1和CPage2。
然后在View類頭文件中,加入這兩個(gè)對(duì)話框?qū)ο蟆M瑫r(shí)增加一個(gè)變量int m_CurSelTab,用了表明是哪個(gè)Page即將被切換。
為了避免用戶在切換Tab時(shí),程序?qū)ab Index的枚舉,可以利用數(shù)組來(lái)做這個(gè)事情。
在View的初始化函數(shù)中需要把CPage1、CPage2和Tab Control關(guān)聯(lián)起來(lái),并保存頁(yè)面地址,設(shè)置初始頁(yè)面,等等。
void CTab_testView::OnInitialUpdate()
{
CFormView::OnInitialUpdate();
GetParentFrame()->RecalcLayout();
ResizeParentToFit();
//為T(mén)ab Control增加兩個(gè)頁(yè)面
m_tab.InsertItem(0, _T("First"));
m_tab.InsertItem(1, _T("Second"));
//創(chuàng)建兩個(gè)對(duì)話框
m_page1.Create(IDD_DIALOG1, &m_tab);
m_page2.Create(IDD_DIALOG2, &m_tab);
//設(shè)定在Tab內(nèi)顯示的范圍
CRect rc;
m_tab.GetClientRect(rc);
rc.top += 20;
rc.bottom -= 8;
rc.left += 8;
rc.right -= 8;
m_page1.MoveWindow(&rc);
m_page2.MoveWindow(&rc);
//把對(duì)話框?qū)ο笾羔槺4嫫饋?lái)
pDialog[0] = &m_page1;
pDialog[1] = &m_page2;
//顯示初始頁(yè)面
pDialog[0]->ShowWindow(SW_SHOW);
pDialog[1]->ShowWindow(SW_HIDE);
//保存當(dāng)前選擇
m_CurSelTab = 0;
}
這里面需要注意的是,我用了一個(gè)CDialog指針數(shù)組來(lái)進(jìn)行保存,數(shù)組的大小是Tab Control頁(yè)面的個(gè)數(shù),數(shù)組下標(biāo)對(duì)應(yīng)著每個(gè)頁(yè)面的索引(這樣方便快速存取)。
用戶切換時(shí),需要響應(yīng)相關(guān)的消息。
void CTab_testView::OnSelchangeTab1(NMHDR* pNMHDR, LRESULT* pResult)
{
// TODO: Add your control notification handler code here
pDialog[m_CurSelTab]->ShowWindow(SW_HIDE);
m_CurSelTab = m_tab.GetCurSel();
pDialog[m_CurSelTab]->ShowWindow(SW_SHOW);
*pResult = 0;
}
首先我們先把當(dāng)前的頁(yè)面隱藏起來(lái),然后得到新的頁(yè)面索引,最后就把相關(guān)頁(yè)面顯示出來(lái)即可。這比一個(gè)個(gè)去枚舉簡(jiǎn)單多了。
還有一點(diǎn)比較有意思,那就是DDX/DDV機(jī)制的運(yùn)用。要想獲得Tab Control各個(gè)頁(yè)面的數(shù)據(jù),可以利用DDX/DDV機(jī)制,但需要注意,因?yàn)檫@是多個(gè)頁(yè)面,所以需要顯式調(diào)用多次。
void CTab_testView::OnButton1()
{
// TODO: Add your control notification handler code here
m_page1.UpdateData();
m_page2.UpdateData();
CString str1 = m_page1.m_str1;
CString str2 = m_page2.m_str2;
AfxMessageBox(str1);
AfxMessageBox(str2);
}
經(jīng)過(guò)這幾步處理,基本上我們就可以利用Tab Control的強(qiáng)大功能了。
抨擊匈牙利命名法
匈牙利命名法是一種編程時(shí)的命名規(guī)范。命名規(guī)范是程序書(shū)寫(xiě)規(guī)范中最重要也是最富爭(zhēng)議的地方,自古乃兵家必爭(zhēng)之地。命名規(guī)范有何用?四個(gè)字:名正言順。用二分法,命名規(guī)范分為好的命名規(guī)范和壞的命名規(guī)范,也就是說(shuō)名正言順的命名規(guī)范和名不正言不順的命名規(guī)范。好的舞鞋是讓舞者感覺(jué)不到其存在的舞鞋,壞的舞鞋是讓舞者帶著鐐銬起舞。一個(gè)壞的命名規(guī)范具有的破壞力比一個(gè)好的命名規(guī)范具有的創(chuàng)造力要大得多。
本文要證明的是:匈牙利命名法是一個(gè)壞的命名規(guī)范。本文的作用范圍為靜態(tài)強(qiáng)類型編程語(yǔ)言。本文的分析范本為C語(yǔ)言和C++語(yǔ)言。下文中的匈法為匈牙利命名法的簡(jiǎn)稱。
一 匈牙利命名法的成本
匈法的表現(xiàn)形式為給變量名附加上類型名前綴,例如:nFoo,szFoo,pFoo,cpFoo分別表示整型變量,字符串型變量,指針型變量和常指針型變量。可以看出,匈法將變量的類型信息從單一地點(diǎn)(聲明變量處)復(fù)制到了多個(gè)地點(diǎn)(使用變量處),這是冗余法。冗余法的成本之一是要維護(hù)副本的一致性。這個(gè)成本在編寫(xiě)和維護(hù)代碼的過(guò)程中需要改變變量的類型時(shí)付出。冗余法的成本之二是占用了額外的空間。一個(gè)優(yōu)秀的書(shū)寫(xiě)者會(huì)自覺(jué)地遵從一個(gè)法則:代碼最小組織單位的長(zhǎng)度以30個(gè)自然行以下為宜,如果超過(guò)50行就應(yīng)該重新組織。一個(gè)變量的書(shū)寫(xiě)空間會(huì)給這一法則添加不必要的難度。
二 匈牙利命名法的收益
這里要證明匈牙利命名法的收益是含糊的,無(wú)法預(yù)期的。
范本1:strcpy(pstrFoo,pcstrFoo2) Vs strcpy(foo,foo2)
匈法在這里有什么收益呢?我看不到。沒(méi)有一個(gè)程序員會(huì)承認(rèn)自己不知道strcpy函數(shù)的參數(shù)類型吧。
范本2:unknown_function(nFoo) Vs unknown_function(foo)
匈法在這里有什么收益呢?我看不到。對(duì)于一個(gè)不知道確定類型的函數(shù),程序員應(yīng)該去查看該函數(shù)的文檔,這是一種成本。使用匈法的唯一好處是看代碼的人知道這個(gè)函數(shù)要求一個(gè)整型參數(shù),這又有什么用處呢?函數(shù)是一種接口,參數(shù)的類型僅僅是接口中的一小部分。諸如函數(shù)的功能、出口信息、線程安全性、異常安全性、參數(shù)合法性等重要信息還是必須查閱文檔。
范本3:nFoo=nBar Vs foo=bar
匈法在這里有什么收益呢?我看不到。使用匈法的唯一好處是看代碼的人知道這里發(fā)生了一個(gè)整型變量的復(fù)制動(dòng)作,聽(tīng)起來(lái)沒(méi)什么問(wèn)題,可以安心睡大覺(jué)了。如果他看到的是nFoo=szBar,可能會(huì)從美夢(mèng)中驚醒。且慢,事情真的會(huì)是這樣嗎?我想首先被驚醒的應(yīng)該是編譯器。另一方面,nFoo=nBar只是在語(yǔ)法上合法而已,看代碼的人真正關(guān)心的是語(yǔ)義的合法性,匈法對(duì)此毫無(wú)幫助。另一方面,一個(gè)優(yōu)秀的書(shū)寫(xiě)者會(huì)自覺(jué)地遵從一個(gè)法則:代碼最小組織單位中的臨時(shí)變量以一兩個(gè)為宜,如果超過(guò)三個(gè)就應(yīng)該重新組織。結(jié)合前述第一個(gè)法則,可以得出這樣的結(jié)論:易于理解的代碼本身就應(yīng)該是易于理解的,這是代碼的內(nèi)建高質(zhì)量。好的命名規(guī)范對(duì)內(nèi)建高質(zhì)量的助益相當(dāng)有限,而壞的命名規(guī)范對(duì)內(nèi)建高質(zhì)量的損害比人們想象的要大。
三 匈牙利命名法的實(shí)施
這里要證明匈牙利命名法在C語(yǔ)言是難以實(shí)施的,在C++語(yǔ)言中是無(wú)法實(shí)施的。從邏輯上講,對(duì)匈法的收益做出否定的結(jié)論以后,再來(lái)論證匈法的可行性,是畫(huà)蛇添足。不過(guò)有鑒于小馬哥曾讓已射殺之?dāng)乘阑覐?fù)燃,我還是再踏上一支腳為妙。
前面講過(guò),匈法是類型系統(tǒng)的冗余,所以實(shí)施匈法的關(guān)鍵是我們是否能夠精確地對(duì)類型系統(tǒng)進(jìn)行復(fù)制。這取決于類型系統(tǒng)的復(fù)雜性。
先來(lái)看看C語(yǔ)言:
1.內(nèi)置類型:int,char,float,double 復(fù)制為 n,ch,f,d?好像沒(méi)有什么問(wèn)題。不過(guò)誰(shuí)來(lái)告訴我void應(yīng)該怎么表示?
2.組合類型:array,union,enum,struct 復(fù)制為 a,u,e,s?好象比較別扭。
這里的難點(diǎn)不是為主類型取名,而是為副類型取名。an表示整型數(shù)組?sfoo,sbar表示結(jié)構(gòu)foo,結(jié)構(gòu)bar?ausfoo表示聯(lián)合結(jié)構(gòu)foo數(shù)組?累不累啊。
3.特殊類型:pointer。pointer在理論上應(yīng)該是組合類型,但是在C語(yǔ)言中可以認(rèn)為是內(nèi)置類型,因?yàn)镃語(yǔ)言并沒(méi)有非常嚴(yán)格地區(qū)分不同的指針類型。下面開(kāi)始表演:pausfoo表示聯(lián)合結(jié)構(gòu)foo數(shù)組指針?ppp表示指針的指針的指針?
噩夢(mèng)還沒(méi)有結(jié)束,再來(lái)看看類型系統(tǒng)更阿為豐富的C++語(yǔ)言:
1.class:如果說(shuō)C語(yǔ)言中的struct還可以用stru搪塞過(guò)去的話,不要夢(mèng)想用cls來(lái)搪塞C++中的class。嚴(yán)格地講,class根本就并不是一個(gè)類型,而是創(chuàng)造類型的工具,在C++中,語(yǔ)言內(nèi)置類型的數(shù)量和class創(chuàng)造的用戶自定義類型的數(shù)量相比完全可以忽略不計(jì)。stdvectorFoo表示標(biāo)準(zhǔn)庫(kù)向量類型變量Foo?瘋狂的念頭。
2.命名空間:boostfilesystemiteratorFoo,表示boost空間filesystem子空間遍歷目錄類型變量Foo?程序員要崩潰了。
3.模板:你記得std::map<std::string,std::string>類型的確切名字嗎?我是記不得了,好像超過(guò)255個(gè)字符,還是饒了我吧。
4.模板參數(shù):template <class T, class BinaryPredicate>const T& max(const T& a, const T& b, BinaryPredicate comp) 聰明的你,請(qǐng)用匈法為T(mén)命名。上帝在發(fā)笑。
5.類型修飾:static,extern,mutable,register,volatile,const,short,long,unsigned 噩夢(mèng)加上修飾是什么?還是噩夢(mèng)。
摘要: 首先,制作交叉工具鏈的目的是為了給我的手機(jī)--MOTO ROKR E2編譯程序。然后,順便學(xué)習(xí)一下嵌入式軟件的開(kāi)發(fā)先說(shuō)一下,搞這個(gè)需要很大的耐心。我用的硬件是Sempron3100+, 512MB內(nèi)存, 編譯環(huán)境是windowsXP + vmware5.5 + gentoo, 在CUI下,花了大概20個(gè)小時(shí)才編譯完(我從晚上八點(diǎn)一直弄到第二天下午四點(diǎn))。1. 準(zhǔn)備源碼: binuitls... 閱讀全文
摘要: 問(wèn)題描述:在一個(gè)N * N的棋盤(pán)上放N個(gè)皇后, 并使皇后們不能互相攻擊,皇后可以橫著、豎著、斜著吃子,即棋盤(pán)上任意兩個(gè)皇后不能同行、同列或在一條對(duì)角線上。以下是我寫(xiě)的代碼:
1#include <iostream> 2#include <stack> 3using namespace std;&nbs... 閱讀全文
取得程序的名字
#include <windows.h>

int main()


{
void *PEB, *Ldr, *Flink, *FullImagePath;
wchar_t *Name = NULL;

__asm

{
mov eax,fs:[0x30]
mov PEB,eax
}
Ldr = *( ( void ** )( ( unsigned char * )PEB + 0x0c ) );
Flink = *( ( void ** )( ( unsigned char * )Ldr + 0x0c ) );

FullImagePath = *( ( void ** )( ( unsigned char * )Flink + 0x28 ) );

Name = wcsrchr((wchar_t*)FullImagePath, 0x5C) + 1;

MessageBoxW(NULL, Name, L"應(yīng)用程序的名字為:", MB_OK );

return(0);
}
