|
用户名:dragon77 笔名:狂龙天下 地区: 广东-广州 行业:其他 |
| 日 | 一 | 二 | 三 | 四 | 五 | 六 |
无网格法研究。 本人现已毕业,硕士毕业论文《无单元伽辽金法程序设计及其应用》,论文主要研究内容:1.EFGM程序设计(VC++);2.基于VC的EFGM分析平台开发,含前、后处理;3.EFGM中各种支座的处理及程序实现;4.EFGM在边坡稳定中的应用实例。
[原创]无网格计算程序界面开发实践
(作者置顶)
学习无网格计算方法已经有一段时间了,现在开始写毕业论文。论文的内容计划是写地下结构的弹粘塑性分析,为了便于调试,我想还是编写一个界面比较节约时间。
在这些方面我也是边学边做,如果大家有更好的办法,还请不吝指教!THX!
主界面划分为三个窗口,如图1所示,上面两个窗口为菜单区(受ansys的影响了^_^)和绘图区(以后可以考虑是否用一个属性页,可以绘图,也可以显示计算结果,看内存消耗情况吧),下面的一个窗口为输出窗口(显示的内容类似于日志形式吧,比如当前正在进行的步骤、花费的时间等)。
主界面
划分窗口的代码如下:
在CMainFrame中声明如下两个变量,为什么在这里使用CMySplitterWnd而不是CSplitterWnd呢?主要是为了重载鼠标的动作,使得分割窗口的大小不可调整:
CMySplitterWnd m_wndSplitter; //第一行的两个列
CMySplitterWnd m_wndSplitter1; //两行
在OnCreateClient中添加以下分割窗口的代码:
BOOL CMainFrame::OnCreateClient(LPCREATESTRUCT /*lpcs*/,
CCreateContext* pContext)
{
// create splitter window
if (!m_wndSplitter1.CreateStatic(this, 2, 1))
return FALSE;
if (!m_wndSplitter1.CreateView(1, 0, RUNTIME_CLASS(CBottomView), CSize(0, 0), pContext))
{
m_wndSplitter1.DestroyWindow();
return FALSE;
}
if(m_wndSplitter.CreateStatic(&m_wndSplitter1,1,2,WS_CHILD|WS_VISIBLE,
m_wndSplitter1.IdFromRowCol(0, 0))==NULL)
return FALSE; //将第0行0列再分开1行2列
if (!m_wndSplitter.CreateView(0, 0, RUNTIME_CLASS(CLeftView), CSize(140, 5000), pContext) ||
!m_wndSplitter.CreateView(0, 1, RUNTIME_CLASS(CMeshlessView), CSize(0, 5000), pContext))
{
m_wndSplitter.DestroyWindow();
return FALSE;
}
m_wndSplitter1.SetRowInfo(0,560,100); //设置第一行窗口的高度
return TRUE;
}
新建CSplitterWnd的派生类CMySplitterWnd,再重载以下函数,将默认的CSplitterWnd调用改为CWnd,使分割窗口失去调整大小的能力:
void CMySplitterWnd::OnLButtonUp(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CWnd::OnLButtonUp(nFlags, point);
//CSplitterWnd::OnLButtonUp(nFlags, point);
}
void CMySplitterWnd::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
//SplitterWnd::OnLButtonDown(nFlags, point);
CWnd::OnLButtonDown(nFlags, point);
}
左侧的树形菜单为CLeftView,基类为CTree。创建各项目的代码如下:
void CLeftView::OnInitialUpdate()
{
CTreeView::OnInitialUpdate();
// TODO: You may populate your TreeView with items by directly accessing
// its tree control through a call to GetTreeCtrl().
m_tree = &(this->GetTreeCtrl());
HTREEITEM parent[10];
// CString text;
// text.Format("this is parent");
parent[0] = m_tree->InsertItem("1 设置工程参数",0,1);
parent[1] = m_tree->InsertItem("2 定义材料",0,1);
parent[2] = m_tree->InsertItem("3 建模",0,1);
parent[3] = m_tree->InsertItem("4 布点",0,1);
parent[4] = m_tree->InsertItem("5 加载",0,1);
parent[5] = m_tree->InsertItem("6 求解",0,1);
HTREEITEM parent_sub1[10][5];
parent_sub1[0][0] = m_tree->InsertItem("1.1 工作目录",0,1,parent[0]);
parent_sub1[0][1] = m_tree->InsertItem("1.2 项目名称",0,1,parent[0]);
parent_sub1[2][0] = m_tree->InsertItem("3.1 求解参数",0,1,parent[2]);
parent_sub1[2][1] = m_tree->InsertItem("3.2 Solve",0,1,parent[2]);
parent_sub1[5][0] = m_tree->InsertItem("6.1 求解参数",0,1,parent[5]);
parent_sub1[5][1] = m_tree->InsertItem("6.2 开始求解",0,1,parent[5]);
以各窗口与输出窗口CBottomView的通信为例,CBottomView的基类为CEditView:
CBottomView.h中定义变量:
CEdit* m_ctrlEdit;
在构造函数中将编辑窗口与它关联:
CBottomView::CBottomView()
{
m_ctrlEdit =& this->GetEditCtrl();
}
CBottomView中定义一个函数,用于在编辑窗口中输入文本内容:
void CBottomView::AppendText(LPCSTR pText)
{
// m_ctrlEdit->SetMargins(20,10);
int nLen=m_ctrlEdit->GetWindowTextLength();
m_ctrlEdit->SetFocus();
m_ctrlEdit->SetSel(nLen,nLen);
m_ctrlEdit->ReplaceSel(pText);
}
在需要的地方,通过以下代码可以调用CBottomView中的功能:
void CMeshlessView::AppendText(LPCSTR pText)
{
// CMainFrame* MainFrame=(CMainFrame*)this->GetParent()->GetParent(); //这种方法会出错!
CMainFrame* MainFrame=static_cast
CBottomView* BottomView=static_cast
BottomView->AppendText(pText);
}
定义全局变量CString strWorkDir和CString strWorkName;方法详见我的博客里的文章“VC中怎么使用全局变量”
处理CLeftView树形视图中的选择事件:
void CLeftView::OnLButtonDown(UINT nFlags, CPoint point)
{
// TODO: Add your message handler code here and/or call default
CTreeView::OnLButtonDown(nFlags, point);
UINT m_uFlags;
HTREEITEM hItem = m_tree->HitTest(point, &m_uFlags);
CString sItemName;
CSelectDir dlgSelectDir;
if ( (m_uFlags&TVHT_ONITEMLABEL ))
{
sItemName = m_tree->GetItemText(hItem);
if(sItemName.Find("工作目录")!=-1) //选择工作目录
{
if(g_fSelectFolderDlg(&strWorkDir,strWorkDir,true))
{
UpdateData(false);
//改变窗口标题栏名称
char str2[255];
strcpy(str2,strWorkDir);
strcat(str2,strWorkName);
strcat(str2," - 无网格计算");
AfxGetMainWnd()->SetWindowText(str2);
}
}
if(sItemName.Find("项目名称")!=-1) //定义项目名称
{
CWorkName workname;
workname.DoModal();
//改变窗口标题栏名称
char str2[255];
strcpy(str2,strWorkDir);
strcat(str2,strWorkName);
strcat(str2," - 无网格计算");
AfxGetMainWnd()->SetWindowText(str2);
}
}
}
选择文件夹的函数,单独定义一个selectdir.h中:另一篇文章:vc文件夹选择对话框
#ifndef GLOBAL_H
#define GLOBAL_H
#ifndef BIF_NEWDIALOGSTYLE
#define BIF_NEWDIALOGSTYLE 0x0040
#endif
#ifndef BIF_USENEWUI
#define BIF_USENEWUI 0x0050
#endif
#define REMOVE_HEIGHT 28
// 初始化文件夹设定用的回调函数
int CALLBACK _SHBrowseForFolderCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData)
{
static HWND hWndEdit = NULL;
CString strDir;
switch (uMsg)
{
case BFFM_INITIALIZED:
{
::SendMessage(hwnd, BFFM_SETSELECTION, TRUE, lpData);
CRect rect;
HWND hChild = GetWindow(hwnd, GW_CHILD);
while (hChild)
{
TCHAR szClass[256];
GetClassName(hChild, szClass, 255);
// 如果是中间的树目录控件,使可以随窗口移动大小
if (strcmp(szClass, "SHBrowseForFolder ShellNameSpace Control") == 0)
{
GetWindowRect(hChild, rect);
rect.top -= REMOVE_HEIGHT;
CPoint pt = rect.TopLeft();
ScreenToClient(hwnd, &pt);
MoveWindow(hChild, pt.x, pt.y, rect.Width(), rect.Height(), TRUE);
}
if (strcmp(szClass, "Edit") == 0)
{
hWndEdit = hChild;
SetWindowText(hWndEdit, strWorkDir);
}
hChild = GetNextWindow(hChild, GW_HWNDNEXT);
}
SetWindowText(hwnd, "工作目录...");
}
&nbs; break;
case BFFM_SELCHANGED: //处理选中的项目,使得编辑框中可以显示文件夹全名
if(hWndEdit)
{
if(::SHGetPathFromIDList((LPITEMIDLIST)lParam, strDir.GetBufferSetLength(MAX_PATH)))
{
SetWindowText(hWndEdit, strDir);
}
else
{
SetWindowText(hWndEdit, NULL);
SendMessage(hwnd, BFFM_VALIDATEFAILED, 0, 0);
}
strDir.ReleaseBuffer();
}
break;
default:
break;
}
return 0;
}
//lpstrFolder返回的文件夹名称
//strIniFolder初始文件夹
//bAvailNewFolder是否允许新建文件夹
bool g_fSelectFolderDlg(CString* lpstrFolder,CString strIniFolder,bool bAvailNewFolder)
{
bool ret;
char lpszPath[MAX_PATH];
LPMALLOC lpMalloc;
BROWSEINFO sInfo;
LPITEMIDLIST lpidlRoot;
LPITEMIDLIST lpidlBrowse;
if(lpstrFolder == NULL)
return false;
if(::SHGetMalloc(&lpMalloc) != NOERROR)
return false;
ret = false;
if(strIniFolder != "")
{
if(strIniFolder.Right(1) == "\\")
strIniFolder = strIniFolder.Left(strIniFolder.GetLength() - 1); //删除末尾的"\\"
}
::SHGetSpecialFolderLocation(NULL, CSIDL_DRIVES, &lpidlRoot); //取得选定的文件夹名
::ZeroMemory(&sInfo, sizeof(BROWSEINFO));
sInfo.pidlRoot = lpidlRoot;
sInfo.pszDisplayName = lpszPath;
sInfo.lpszTitle = _T("选择工作目录:");
sInfo.ulFlags = BIF_RETURNONLYFSDIRS| BIF_NEWDIALOGSTYLE | BIF_USENEWUI;
// if(bAvailNewFolder == true)
// sInfo.ulFlags |= BIF_EDITBOX | BIF_NEWDIALOGSTYLE | BIF_USENEWUI;
sInfo.lpfn = _SHBrowseForFolderCallbackProc;
sInfo.lParam = (LPARAM)strIniFolder.GetBuffer(0);
lpidlBrowse = ::SHBrowseForFolder(&sInfo); //显示文件夹选择对话框
if(lpidlBrowse != NULL)
{
if(::SHGetPathFromIDList(lpidlBrowse,lpszPath)) //取得文件夹名
{
*lpstrFolder = "";
*lpstrFolder = lpszPath;
if(*lpstrFolder != "")
{
if(lpstrFolder->Right(1) != "\\")
*lpstrFolder += "\\"; //在末尾时附加"\\"
}
}
ret = true;
}
if(lpidlBrowse != NULL)
::CoTaskMemFree(lpidlBrowse);
if(lpidlRoot != NULL)
::CoTaskMemFree(lpidlRoot);
lpMalloc->Release();
return ret;
}
#endif
运行结果如图2:

图2 文件夹选择对话框
定义项目名称的程序很简单,只需在对话框的类向导中定义一个与编辑框关联的CString,再在程序中将这个变量删除,把全局变量strWorkName与编辑框关联就OK了。改变的内容如下:
void CWorkName::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
//{{AFX_DATA_MAP(CWorkName)
DDX_Text(pDX, IDC_EDIT_NEW, strWorkName);
//}}AFX_DATA_MAP
}
运行结果如图3:

图3 定义项目名称
[原创]QQ斗地主记牌器开发实践
(作者置顶)
孙盒淞珍藏两年的精品:图象记忆的基本公式
图象记忆的基本公式
神奇记忆术揭秘(1-5)
伟大的中文--只能阅不能读的文章
有钱没钱
| 没钱的时候,养猪 有钱的时候,养狗。 没钱的时候,在家里吃野菜 有钱的时候,在酒店吃野菜。 没钱的时候,在马路上骑自行车 有钱的时候,在客厅里骑自行车。 没钱的时候想结婚, 有钱的时候想离婚 没钱的时候老婆兼秘书, 有钱的时候秘书兼老婆 没钱的时候假装有钱, 有钱的时候假装没钱 人啊,都讲实话: 说股票是毒品,都在玩; 说金钱是罪恶,都在捞; 说美女是祸水,都想要; 说高处不胜寒,都在爬; 说烟酒伤身体,就不戒; 说天堂最美好,都不去!!! 当今社会,穷吃肉,富吃虾,领导干部吃王八; 男想高,女想瘦, 狗穿衣裳人露肉; 过去把第一次留给丈夫 现在把第一胎留给丈夫; 乡下早晨鸡叫人, 城里晚上人叫鸡; 旧社会戏子卖艺不卖身, 新社会演员卖身不卖艺 工资真的要涨了 心里更加爱党了 能给孩子奖赏了 见到老婆敢嚷了 敢尝海鲜鹅掌了 闲时能逛商场了 遇见美女心痒了 结果物价又涨了 一切都他妈白想了 |
07年总结?
祝大家新年快乐,2007年总结如下:
存在问题:好吃饭、好泡妞、好抽烟、好喝酒。|
分析原因:饭好吃、妞好泡、烟好抽、酒好喝。
总结经验:吃饭好、泡妞好、抽烟好、喝酒好。
整改措施:饭吃好、妞泡好、烟抽好、酒喝好。
奋斗目标:吃好饭、泡好妞、抽好烟、喝好酒。
07年总结?
祝大家新年快乐,2007年总结如下:
存在问题:好吃饭、好泡妞、好抽烟、好喝酒。|
分析原因:饭好吃、妞好泡、烟好抽、酒好喝。
总结经验:吃饭好、泡妞好、抽烟好、喝酒好。
整改措施:饭吃好、妞泡好、烟抽好、酒喝好。
奋斗目标:吃好饭、泡好妞、抽好烟、喝好酒。
Effective c++:第二版 第一章 从C转向C++
对每个人来说,习惯C++需要一些时间,对于已经熟悉C的程序员来说,这个过程尤其令人苦恼。因为C是C++的子集,所有的C的技术都可以继续使用,但很多用起来又不合适。例如,C++程序员会认为指针的指针看起来很古怪,他们会问:为什么不用指针的引用来代替呢?
C是一种简单的语言。它真正提供的只有有宏、指针、结构、数组和函数。不管什么问题,C都靠宏、指针、结构、数组和函数来解决。而C++不是这样。宏、指针、结构、数组和函数当然还存在,此外还有私有和保护型成员、函数重载、缺省参数、构造和析构函数、自定义操作符、内联函数、引用、友元、模板、异常、名字空间,等等。用C++比用C具有更宽广的空间,因为设计时有更多的选择可以考虑。
在面对这么多的选择时,许多C程序员墨守成规,坚持他们的老习惯。一般来说,这也不是什么很大的罪过。但某些C的习惯有悖于C++的精神本质,他们都在下面的条款进行了阐述。
这个条款最好称为:“尽量用编译器而不用预处理”,因为#define经常被认为好象不是语言本身的一部分。这是问题之一。再看下面的语句:
#define ASPECT_RATIO 1.653
编译器会永远也看不到ASPECT_RATIO这个符号名,因为在源码进入编译器之前,它会被预处理程序去掉,于是ASPECT_RATIO不会加入到符号列表中。如果涉及到这个常量的代码在编译时报错,就会很令人费解,因为报错信息指的是1.653,而不是ASPECT_RATIO。如果ASPECT_RATIO不是在你自己写的头文件中定义的,你就会奇怪1.653是从哪里来的,甚至会花时间跟踪下去。这个问题也会出现在符号调试器中,因为同样地,你所写的符号名不会出现在符号列表中。
解决这个问题的方案很简单:不用预处理宏,定义一个常量:
const double ASPECT_RATIO = 1.653;
这种方法很有效。但有两个特殊情况要注意。
首先,定义指针常量时会有点不同。因为常量定义一般是放在头文件中(许多源文件会包含它),除了指针所指的类型要定义成const外,重要的是指针也经常要定义成const。例如,要在头文件中定义一个基于char*的字符串常量,你要写两次const:
const char * const authorName = "Scott Meyers";
关于const的含义和用法,特别是和指针相关联的问题,参见条款21。
另外,定义某个类(class)的常量一般也很方便,只有一点点不同。要把常量限制在类中,首先要使它成为类的成员;为了保证常量最多只有一份拷贝,还要把它定义为静态成员:
class GamePlayer {
private:
static const int NUM_TURNS = 5; // constant eclaration
int scores[NUM_TURNS]; // use of constant
...
};
还有一点,正如你看到的,上面的语句是NUM_TURNS的声明,而不是定义,所以你还必须在类的实现代码文件中定义类的静态成员:
const int GamePlayer::NUM_TURNS; // mandatory definition; // goes in class impl.file
你不必过于担心这种小事。如果你忘了定义,链接器会提醒你。
旧一点的编译器会不接受这种语法,因为它认为类的静态成员在声明时定义初始值是非法的;而且,类内只允许初始化整数类型(如:int, bool, char 等),还只能是常量。
在上面的语法不能使用的情况下,可以在定义时赋初值:
class EngineeringConstants { // this goes in the class
private: // header file
static const double FUDGE_FACTOR;
...
};
// this goes in the class implementation file
const double EngineeringConstants::FUDGE_FACTOR = 1.35;
大多数情况下你只要做这么多。唯一例外的是当你的类在编译时需要用到这个类的常量的情况,例如上面GamePlayer::scores数组的声明(编译过程中编译器一定要知道数组的大小)。所以,为了弥补那些(不正确地)禁止类内进行整型类常量初始化的编译器的不足,可以采用称之为“借用enum”的方法来解决。这种技术很好地利用了当需要int类型时可以使用枚举类型的原则,所以GamePlayer也可以象这样来定义:
class GamePlayer {
private:
enum { NUM_TURNS = 5 } // "the enum hack" — makes
// NUM_TURNS a symbolic name
// for 5
int scores[NUM_TURNS];// fine
};
除非你正在用老的编译器(即写于1995年之前),你不必借用enum。当然,知道有这种方法还是值得的,因为这种可以追溯到很久以前的时代的代码可是不常见的哟。
回到预处理的话题上来。另一个普遍的#define指令的用法是用它来实现那些看起来象函数而又不会导致函数调用的宏。典型的例子是计算两个对象的最大值:
#define max(a,b) ((a) > (b) ? (a) : (b))
这个语句有很多缺陷,光想想都让人头疼,甚至比在高峰时间到高速公路去开车还让人痛苦。
无论什么时候你写了象这样的宏,你必须记住在写宏体时对每个参数都要加上括号;否则,别人调用你的宏时如果用了表达式就会造成很大的麻烦。但是即使你象这样做了,还会有象下面这样奇怪的事发生:
int a = 5, b = 0; max(++a, b);// a 的值增加了2次 max(++a, b+10); // a 的值只增加了1次
这种情况下,max内部发生些什么取决于它比较的是什么值!
幸运的是你不必再忍受这样愚笨的语句了。你可以用普通函数实现宏的效率,再加上可预计的行为和类型安全,这就是内联函数(见条款33):
inline int max(int a, int b) { return a > b ? a : b; }不过这和上面的宏不大一样,因为这个版本的max只能处理int类型。但模板可以很轻巧地解决这个问题:
template<class T>
inline const T& max(const T& a, const T& b)
{ return a > b ? a : b; }
这个模板产生了一整套函数,每个函数拿两个可以转换成同种类型的对象进行比较然后返回较大的(常量)对象的引用。因为不知道T的类型,返回时传递引用可以提高效率(见条款22)。
顺便说一句,在你打算用模板写象max这样有用的通用函数时,先检查一下标准库(见条款49),看看他们是不是已经存在。比如说上面说的max,你会惊喜地发现你可以后人乘凉:max是C++标准库的一部分。
有了const和inline,你对预处理的需要减少了,但也不能完全没有它。抛弃#include的日子还很远,#ifdef/#ifndef在控制编译的过程中还扮演重要角色。预处理还不能退休,但你一定要计划给它经常放长假。
是的,scanf和printf很轻巧,很高效,你也早就知道怎么用它们,这我承认。但尽管他们很有用,事实上scanf和printf及其系列还可以做些改进。尤其是,他们不是类型安全的,而且没有扩展性。因为类型安全和扩展性是C++的基石,所以你也要服从这一点。另外,scanf/printf系列函数把要读写的变量和控制读写格式的信息分开来,就象古老的FORTRAN那样。是该向五十年代说诀别的时候了!
不必惊奇,scanf/printf的这些弱点正是操作符>>和<<的强项:
int i; Rational r;// r 是个有理数 ... cin >> i >> r; cout << i << r;
上面的代码要通过编译,>>和<<必须是可以处理Rational类型对象的重载函数(可能要通过隐式类型转换)。如果没有实现这样的函数,就会出错(处理int不用这样做,因为它是标准用法)。另外,编译器自己可以根据不同的变量类型选择操作符的不同形式,所以不必劳你去指定第一个要读写的对象是int而第二个是Rational。
另外,在传递读和写的对象时采用的语法形式相同,所以不必象scanf那样死记一些规定,比如如果没有得到指针,必须加上地址符,而如果已经得到了指针,又要确定不要加上地址符。这些完全可以交给C++编译器去做。编译器没别的什么事好做的,而你却不一样。最后要注意的是,象int这样的固定类型和象Rational这样的自定义类型在读写时方式是一样的。而你用sacnf和printf试试看!
你所写的表示有理数的类的代码可能象下面这样:
class Rational {
public:
Rational(int numerator = 0, int denominator = 1);
...
private:
int n, d;// 分子,分母
friend ostream& operator<<(ostream& s, const Rational& );
};
ostream& operator<<(ostream& s, const Rational& r)
{
s<< r.n << '/' << r.d;
return s;
}
上面的代码涉及到operator<<的一些微妙(但很重要)的用法,这在本书其他地方详细讨论。例如:上面的operator<<不是成员函数(条款19解释了为什么),而且,传递给operator<<的不是Rational对象,而是定义为const的对象的引用(参见条款22)。operator>>的声明和实现也类似。
尽管我不大愿意承认,可有些情况下回到那些经过证明而且正确的老路上去还是很有意义的。第一,有些iostream的操作实现起来比相应的C stream效率要低,所以不同的选择会给你的程序有可能(虽然不一定,参见条款M16)带来很大的不同。但请牢记,这不是对所有的iostream而言,只是一些特殊的实现;参见条款M23。第二,在标准化的过程中,iostream库在底层做了很多修改(参见条款49),所以对那些要求最大可移植性的应用程序来说,会发现不同的厂商遵循标准的程度也不同。第三,iostream库的类有构造函数而<stdio.h>里的函数没有,在某些涉及到静态对象初始化顺序的时候,如果可以确认不会带来隐患,用标准C库会更简单实用。
iostream库的类和函数所提供的类型安全和可扩展性的价值远远超过你当初的想象,所以不要仅仅因为你用惯了<stdio.h>而舍弃它。毕竟,转换到iostream后,你也不会忘掉<stdio.h>。
顺便说一句,本条款的标题没有打印错;我确实说的是<iostream>而非<iostream.h>。从技术上说,其实没有<iostream.h>这样的东西——标准化委员会在简化非C标准头文件时用<iostream>取代了它。他们这样做的原因在条款49进行了解释。还必须知道的是,如果编译器同时支持 <iostream>和<iostream.h>,那头文件名的使用会很微妙。例如,如果使用了#include <iostream>, 得到的是置于名字空间std(见条款28)下的iostream库的元素;如果使用#include <iostream.h>,得到的是置于全局空间的同样的元素。在全局空间获取元素会导致名字冲突,而设计名字空间的初衷正是用来避免这种名字冲突的发生。还有,打字时<iostream>比<iostream.h>少两个字,这也是很多人用它的原因。:)
条款5:这里为什么要加上个"[]" 调用free将会释放stringarray1指向的内存,但内存里的string对象不会调用析构函数。如果string对象象一般情况那样,自己已经分配了内存,那这些内存将会全部丢失。相反,当对stringarray2调用delete时,数组里的每个对象都会在内存释放前调用析构函数。 既然new和delete可以这么有效地与构造函数和析构函数交互,选用它们是显然的。 把new和delete与malloc和free混在一起用也是个坏想法。对一个用new获取来的指针调用free,或者对一个用malloc获取来的指针调用delete,其后果是不可预测的。大家都知道“不可预测”的意思:它可能在开发阶段工作良好,在测试阶段工作良好,但也可能会最后在你最重要的客户的脸上爆炸。 new/delete和malloc/free的不兼容性常常会导致一些严重的复杂性问题。举个例子,<string.h>里通常有个strdup函数,它得到一个char*字符串然后返回其拷贝: char * strdup(const char *ps); // 返回ps所指的拷贝 在有些地方,c和c++用的是同一个strdup版本,所以函数内部是用malloc分配内存。这样的话,一些不知情的c++程序员会在调用strdup后忽视了必须对strdup返回的指针进行free操作。为了防止这一情况,有些地方会专门为c++重写strdup,并在函数内部调用了new,这就要求其调用者记得最后用delete。你可以想象,这会导致多么严重的移植性问题,因为代码中strdup以不同的形式在不同的地方之间颠来倒去。 c++程序员和c程序员一样对代码重用十分感兴趣。大家都知道,有大量基于malloc和free写成的代码构成的c库都非常值得重用。在利用这些库时,最好是你不用负责去free掉由库自己malloc的内存,并且/或者,你不用去malloc库自己会free掉的内存,这样就太好了。其实,在c++程序里使用malloc和free没有错,只要保证用malloc得到的指针用free,或者用new得到的指针最后用delete来操作就可以了。千万别马虎地把new和free或malloc和delete混起来用,那只会自找麻烦。 既然malloc和free对构造函数和析构函数一无所知,把malloc/free和new/delete混起来用又象嘈杂拥挤的晚会那样难以控制,那么,你最好就什么时候都一心一意地使用new和delete吧。 条款4:尽量使用c++风格的注释旧的c注释语法在c++里还可以用,c++新发明的行尾注释语法也有其过人之处。例如下面这种情形: if ( a > b ) {
// int temp = a; // swap a and b
// a = b;
// b = temp;
}
假设你出于某种原因要注释掉这个代码块。从软件工程的角度看,写这段代码的程序员也做得很好,他最初的代码里也写了一个注释,以解释代码在做什么。用c++形式的句法来注释掉这个程序块时,嵌在里面的最初的注释不受影响,但如果选择c风格的注释就会发生严重的错误: if ( a > b ) {
/* int temp = a; /* swap a and b */
a = b;
b = temp;
*/
}
请注意嵌在代码块里的注释是怎么无意间使本来想注释掉整个代码块的注释提前结束的。 c风格的注释当然还有它存在的价值。例如,它们在c和c++编译器都要处理的头文件中是无法替代的。尽管如此,只要有可能,你最好尽量用c++风格的注释。 值得指出的是,有些老的专门为c写的预处理程序不知道处理c++风格的注释,所以象下面这种情形时,事情就不会象预想的那样: #define light_speedp 3e8 // m/sec (in a vacuum) 对于不熟悉c++的预处理程序来说,行尾的注释竟然成为了宏的一部分!当然,正象条款1所说的那样,你无论如何也不会用预处理来定义常量的。 |
|||||||||||||||
Messages窗口 |
图1 MFC ClassWizard窗口
VC中用KeyPress表示键盘响应的所有事件,包括OnKeyDown事件(某个键按下时)、 OnKeyUp事件(某个键按下后弹起时)和 OnKeyPress事件(当按了某个键时)。(在下面用到的Object1均代表象窗体、按钮、文本框等所有可视化的控件或在程序设计中根据需要添加的成员、类等)
OnKeyuUp事件的语法为 void Object1:: OnKeyUp( UINT nChar, UINT nRepCnt, UINT nFlags );
OnKeyDown事件的语法为 void Object1:: OnKeyDown( UINT nChar, UINT nRepCnt, UINT nFlags );
这里参数nChar代表的是键盘上各键的代码,是编写程序时必须要写明的,而参数nRepCnt,和nFlags则可以使用默认值而不必编写。
下面说明的是在VC的KeyPress事件中的nChar代码与键盘中各键的对应关系:
VK_0 到VK_9 表示键盘上数字“0”到“9”键(ASCII码为0x30 - 0x39);
VK_A 到VK_Z 表示键盘上字母“A”到“Z”键(ASCII码为0x41 - 0x5a);
VK_ADD 表示数字键盘上的“+”键。
VK_ALT 表示键盘上 “ALT”键。
VK_BACK_QUOTE 表示键盘上 “ `”键。
VK_BACK_SLASH 表示键盘上 “\”键。
VK_BACK_SPACE 表示键盘上 “BACKSPACE“键。
VK_CAPS_LOCK 表示键盘上 “CAPS LOCK”键。
VK_CLOSE_BRACKET 表示键盘上 “]”键。
VK_COMMA 表示键盘上 “,”键。
VK_CONTROL 表示键盘上 “CTRL”键。
VK_DECIMAL 表示数字键盘上 “.”键。
VK_DELETE 表示键盘上 “DELETE”键。
VK_DIVIDE 表示键盘上 “/”键。
VK_DOWN 表示键盘上 “向下箭头”键。
VK_END 表示键盘上 “END”键。
VK_ENTER 表示键盘上 “ENTER”键。
VK_EQUALS 表示键盘上 “=”键。
VK_ESCAPE 表示键盘上 “ESC”键。
VK_F1 到VK_F12表示键盘上 “F1”到“F12”键。
VK_HOME 表示键盘上 “HOME”键。
VK_INSERT 表示键盘上 “INSTERT”键。
VK_LEFT 表示键盘上 “向左箭头”键。
VK_MULTIPLY 表示键盘上 “*”键。
VK_NUMPAD0 到VK_NUMPAD9表示数字键盘上 “0”到“9”键。
VK_OPEN_BRACKET表示键盘上 “[”键。
VK_PAGE_DOWN 表示键盘上 “PAGE DOWN”键。
VK_PAGE_UP 表示键盘上 “PAGE UP”键。
VK_PAUSE表示键盘上 “PAUSE”键。
VK_PRINTSCREEN 表示键盘上 “PRINT SCREEN”键。
VK_RIGHT 表示键盘上 “向右箭头”键。
VK_SCROLL_LOCK 表示键盘上 “SCROLL LOCK”键。
VK_SEMICOLON 表示键盘上 “;”键。
VK_SHIFT 表示键盘上 “SHIFT”键。
VK_SPACE 表示键盘上 “SPACEBAR”键。
VK_SUBTRACT表示键盘上 “-”键。
VK_TAB 表示键盘上 “TAB”键。
VK_UP 表示键盘上 “向上箭头”键。
用键盘在人机交互中完成对屏幕中物体位置移动的控制功能时常会用到上、下、左、右几个方向键。下面的程序可以完成对某一可视控件Object1的位置进行控制(分别按下上、下、左、右键使其分别向上、下、左、右方向移动5个屏幕单位):
{
CWnd::OnKeyDown(UINT nChar, UINT nRecpCnt, UNIT nFlages);
Switch(nChar)
{
case VK_LEFT;
Object1.left=Object1.left-5; //按下左键控件左移5个单位
break;
case VK_RIGHT;
Object1.left=Object1.left+5; //按下右键控件右移5个单位
case VK_UP;
Object1.top=Object1.top-5; //按下上键控件上移5个单位
break;
case VK_DOWN;
Object1.top=Object1.top+5; //按下下键控件下移5个单位
break;
defult:
break;
}
}
2.对鼠标的响应
VC中对鼠标的响应常用的有以下事件:
a. OnLButtonDblClk:语法为void Object1 Wnd::OnLButtonDblClk( UINT nFlags, CPoint point ){},表示控件对双击鼠标左键时的响应。
b. OnLButtonDown:语法为void Object1 Wnd::OnLButtonDown( UINT nFlags, CPoint point ){},表示控件对按下鼠标左键时的响应。
c. OnLButtonUp:语法为void Object1 Wnd::OnLButtonUp( UINT nFlags, CPoint point ){},表示控件对按下的鼠标左键放开后的响应。
d. OnRButtonDblClk:语法为void Object1 Wnd::OnRButtonDblClk( UINT nFlags, CPoint point ){},表示控件对双击鼠标右键时的响应。
e. OnRButtonDown:语法为void Object1 Wnd::OnRButtonDown( UINT nFlags, CPoint point ){},表示控件对按下鼠标右键时的响应。
f. OnRButtonUp:语法为void Object1 Wnd::OnRButtonUp( UINT nFlags, CPoint point ){},表示控件对按下的鼠标右键放开后的响应。
其中的参数nFlags用于指示按下的键的代码,它可以是下面的几个值或它们的组合:
MK_CONTROL 表示按下CTRL键 ;
MK_LBUTTON 表示按下鼠标左键;
MK_MBUTTON 表示按下鼠标中键;
MK_RBUTTON 表示按下鼠标右键;
MK_SHIFT 表示按下 SHIFT键;
Point参数表示鼠标的光标位置相对于所在窗口的左上角的水平和垂直坐标值。
在VC中用鼠标响应事件来完成人机交互中的控制功能是比较容易的。例如把一个控件Object1的位置移动到鼠标光标的位置,下面简单的程序就可完成:
void Object1::OnLButtonDown(UINT nFlags, CPoint point)
{
CPoint Position;
Position = point;
Object1->left=Position.x;
Object1->top=Position.y; //按下鼠标左键后控件的水平和垂直方向的值分别与鼠标的光标位置一致
}
3.对游戏操纵杆的响应
VC中对游戏操纵杆的响应是利用Win32 API的MCI提供的一组检测游戏操纵杆、确定操纵杆性能、位置、按钮信息的函数来实现的。这些函数主要包括:
JoyGetNumDevs 检查驱动器支持的游戏操纵杆数
JoyGetPosEx 确定游戏操纵杆是否已连接
JoyGetDevCaps 获取游戏操纵杆性能的信息
JoySetCapture 通知Windows操纵杆消息应发送到哪里以及发送的频率
JoyReleaseCpture 释放指定的游戏操纵杆
其中最常用是获取游戏操纵杆消息的函数,它的语法为joyGetPos(JoystickID,&JoyInfo)。这里的参数JoystickID,表示连接的游戏杆是ID1还是ID2;JoyInfo表示的是位置信息,它有三个位置信息JoyInfo.wXpos、JoyInfo.wYpos、JoyInfo.wZpos分别代表在空间x、y、z方向上的位置,这些位置信息的值一般为0到65535,反映了游戏杆从一端拉满到另一端的最大位置变化为65535。
游戏操纵杆在某些需要对目标位置进行控制的操作中是非常方便的。下面的程序可以完成利用游戏杆操纵来控制一个控件(Object1)位置的功能:
在程序的头文件中需要加入“#include
JOYINFO JoyInfo;
joyGetPos (JoystickID,&JoyInfo); // 获取游戏杆信息
int X,Y;
X = JoyInfo.wXpos; //X为游戏杆的水平方向的值
Y=JoyInfo.wYpos; //Y为游戏杆的垂直方向值
Object1.left=X;
Object1.top=Y; //使控件的位置与游戏杆提供的位置信息一致
JoyReleaseCapture(JoystickID); //释放游戏杆信息
四、结论
由于VC功能强大,又是Microsoft公司大力推广的产品,因此在编写基于Windows操作系统的应用程序方面占有不可替代的位置。尤其在工程开发中,通过编写人-机交互的程序来实现对计算机及外部设备的控制功能非常重要,而这些控制的实施主要是利用文中提到输入设备的响应,因此准确理解和使用其编程方法在工程实践中有重要的意义。本文中的例程均已在Windows98平台上的VC6.0中编译通过。
参考文献
1 乔林等,Visual C++6.0高级编程技术。中国铁道出版社,2000,北京
2 Microsoft Corporation ,Win32 Programmer Reference。Microsoft Press,1995
3 武永康,Direct3D原理与API参考。清华大学出版社,2001
作者简介:陆斌,男,36岁,硕士,讲师,主要从事计算机图像处理方面的教学、科研工作。
通信地址:烟台 264001 海军航空工程学院自动控制系 电话:0535-6224311-37675。E-mail: paine-l@163.net
最深刻的黄段子:骆驼的故事
//全选主元高斯消去法求解线性方程组Ax=b
//当前矩阵为A,mtxResult为结果x
BOOL Matrix::GaussSolve(const Matrix b,Matrix& mtxResult)
{
if (r != b.GetRow()) //判断是否方阵
{
AfxMessageBox("高斯求解出错:矩阵维数不合!");
return FALSE;
}
Matrix mtxA(*this); //备份
mtxResult=b;
Matrix temp(r,1);
long row;
long* colList; //保存交换的列
double dMax=0.0;
colList = new long[r];
for (long k=0; k<r; k++)
colList[k]=k;
for (k=0; k<r-1; k++)
{
dMax=0.0;
long row1;
for (row = k; row < r; row++) //全选主元
for(long col=k;col<r;col++)
if(fabs(mtxA(row,col)) > dMax)
{
dMax = fabs(mtxA(row,col));
row1=row;
colList[k]=col;
}
if (dMax < EPS) //若主元为0,则其右下角子阵全为零,奇异
{
AfxMessageBox("高斯求解出错:矩阵奇异!");
delete[] colList;
return FALSE;
}
if (row1 !=k) //交换行
{
mtxA.SwapRow(k, row1);
mtxResult.SwapRow(k, row1);
}
if(colList[k]!=k) //交换列
mtxA.SwapCol(k,colList[k]);
for (row=k+1; row<r; row++) //计算第k次变换的计算系数
{
double temp; //系数
temp = mtxA(row,k)/mtxA(k,k);
for (long col=k; col<c; col++)
mtxA.SetElement(row,col, mtxA(row,col)-temp* mtxA(k,col));
mtxResult.SetElement(row,0,mtxResult(row,0)-temp*mtxResult(k,0));
}
}
//回代
for(row=r-1;row>=0;row--)
{
for(k=r-1;k>row;k--)
{
double temp;
temp=mtxResult(row,0)-mtxA(row,k)*mtxResult(k,0);
mtxResult.SetElement(row,0,temp);
}
mtxResult.SetElement(row,0,mtxResult(row,0)/mtxA(row,row));
}
//将原来交换的列换回来,在这里我曾经范了一个错误,就是将循环从1到c-1,而不是从c-1到1,结果完全错误!
for(long col = c-1; col>=0;col--)
if(colList[col]!=col)
mtxResult.SwapRow(col,colList[col]);
delete[] colList;
return TRUE;
}