|
|
2007年7月13日
摘要:  这里主要是我最近一段时间在中国编程论坛中发表的C语言板块的帖子和回帖等,主要选取了我个人发表的一些原创C语言代码(全部采用TC2.0进行编译),有少量属于我的一部分研究和学习过程中写的非原创性代码在说明文件中有注明,还有很多比较优秀的我收集的代码,因为并非我的原创而不位于该压缩包内。OUTPUT文件夹中有一部分代码的编译结果,对代码这里就不再一一讲解了。 阅读全文
(1)已知一个位图句柄(HBITMAP),如何获取位图的宽度和高度?
在C#中非常简单,只需要直接访问bitmap.Width和Height属性即可。
在Platform SDK中,GetBitmapDimensionEx是不能完成这个功能的(它需要事先调用SetBitmapDimensionEx),
而使用GetObject函数来获取GDIOBJECT的信息,如下代码:
HBITMAP hBitmap;
BITMAP bminfo;
GetObject(hBitmap, sizeof(BITMAP), &bminfo);
从 bminfo.bmWidth 以及 bmHeight属性可以得到。
(2)如何从一个本地文件路径加载一个HBITMAP?
LoadBitmap可以从HINSTANCE中加载位图资源,但是无法从文件名中加载。要完成这个任务,需要使用LoadImage。
例如:
char* strFileName="c:\\a.bmp";
HBITMAP hBitmap= (HBITMAP)LoadImage(NULL, strFileName, IMAGE_BITMAP,0,0,LR_LOADFROMFILE);
但LoadImage函数只能加载BMP文件,无法成功加载JPG格式。如果要加载JPG格式,应使用OleLoadPicturePath获取一个IPicture接口对象,调用其Render函数在指定的DC进行绘制。
(3)如何使ActiveX控件重绘?
要使在IE中的ActiveX控件,可以调用this->FireViewChange();
(to be continued...)
摘要: 本文分析了系统设计师教程中的数据结构章节的部分代码,并分析和图解了对树和图两种基本数据结构的遍历。 阅读全文
发布一个在wince操作系统下,采用.net compact framework 1.0 ( c#)开发的矢量图控件,我于2007年3月份集中一个月的经历完成了它。当然,它的前身是2005年12月我写的第一个矢量图控件,在后来我在此基础上改进了很多地方,针对具体应用做了重新设计并集中精力把它完成,由于属于再次设计,因此它的架构设计和编码中凝聚了我此前开发中的经验和积累,它是我当时最满意的一个模块,对它的架构和可维护性以及运行性能都感到令我感到非常满意,可以说代表了我当时的最高水平。当然从今天来看里面还有一些缺点和经验不足之处。
例子代码我命名为GisCtl,是因为它原本是希望完成GIS的功能,但是实际上距离GIS还有一定的距离和偏差。该模块的底层算法和全部编码全部是我独自设计,这其中涉及了一系列图形捕捉算法,图元几何变换和逆变换,图层管理,缩略图,视图参数缓存环形队列,文件格式定义和IO等等。在编写该模块中的一些算法时我积累了很厚的演算草稿纸,用到了很多数学和几何知识,当然也感谢飘渺水云间BBS上ZOL版一些热心网友的支持和对我的解答。在编写这个模块时,涉及到一些设计模式,数据结构,算法的应用,比如鼠标在图上点击选择对象时,有一系列判别算法。比如图元和链接对象采用了是类似flyweight模式以节省内存(在我的另一篇随笔《图元几何变换与flyweight模式》中有讲解),比如视图缓存采用了环形的数据结构(在《环形视图堆栈》中有讲述),比如导航图采用了观察者(订阅)模式。该模块的使用方法集中在例子里,这里就不再叙述了。文档说明还暂时不能提供。目前该模块能读写我的自定义格式图形文件,以及mif格式文件。由于需求,该控件没有包装例如用鼠标创建,拖曳,变换对象的操作,但可以用代码来实现这些功能。
这个模块属于我实际工作的项目的一部分。我希望它能发挥更大的价值,而不仅仅是沉寂。
该模块文件名是LineViewCtl.dll, 命名控件是LineViewLib;该dll位于压缩包内,项目中也附带了两个从实际系统中导出的图形文件。
在模拟器中运行的效果图:(由于模拟器不支持中文,所以中文没能正确显示)

范例代码:
http://www.cnblogs.com/Files/hoodlum1980/JRL_GisCtlDemo.rar
这是一个小的例子在于演示SendMessage和PostMessage的区别,其区别简单来说,就是前者会等待对方的窗口过程返回,后者则仅仅给对方的消息队列中投放一个消息立即返回,不会阻塞。因此两个函数的返回值不同,前者为窗口过程的返回值(LRESULT),后者仅投递消息,因此仅返回BOOL表示是否投递成功。

源代码下载链接(VC6):
http://www.cnblogs.com/Files/hoodlum1980/SendMsgTest.rar
【声明】严格来讲,这篇文章不属于我的原创。我在这里参考了codeproject上的国外作者的模仿MSN浮出窗口的C#代码。换句话说,可以认为我把C#代码翻译成了C++代码。另外,为了简化代码,CloseButton我没有采用自己绘制,而是用一个ImageButton来代替。
效果如图所示:
窗口浮出时,停靠于屏幕右下角的位置,这里我借用了博客园的图标(仅用作范例),该窗口主要由用户自定义绘制完成。同时,为了防止浮出窗口夺取焦点, SWP_NOACTIVATE标识似乎有时候并没有很好的符合预期,因此我在重设窗口位置的前后强行设置了前台窗口。
该示例的源代码(VC6.0 + Windows Platform SDK)下载:
http://www.cnblogs.com/Files/hoodlum1980/JRL.NotifyWndDemo.rar
有的选项可能仅提供C++和Windows Platform SDK中的方法(API):
(1)将窗口设为顶层窗口:
c++:
//[注意]:BringWindowToTop( hwnd )不会使窗口成为TopMost窗口!
SetWindowPos(hDlg,HWND_TOPMOST,0,0,0,0,SWP_NOSIZE|SWP_NOMOVE); //忽略x,y,cx,cy参数
c#:
form1.TopMost=true;
(2)设置窗口透明度:
//将窗口设置为图层窗口样式
SetWindowLong(hDlg,GWL_EXSTYLE, GetWindowLong(hDlg, GWL_EXSTYLE) | WS_EX_LAYERED);
//设置alpha值,从0~255
BYTE alpha=200;
SetLayeredWindowAttributes(hDlg,0, alpha, LWA_ALPHA);
(3)已知一个窗口句柄,获取窗口所在程序的HINSTANCE:
HINSTANCE hInstance = (HINSTANCE)GetWindowLong(m_hParentWnd, GWL_HINSTANCE);
(4)获取光标位置:
BOOL GetCursorPos(LPPOINT lpPoint);
(5)使对话框中的某个TextBox(或其他控件)具有输入焦点:
HWND SetFocus(HWND hWnd);
如果该方法不能工作,则可以模拟发送一个鼠标点击的消息给该控件:
PostMessage(hWnd, WM_LBUTTONDOWN, 0, 0);//表示于客户区坐标(0,0)处单击
PostMessage(hWnd, WM_LBUTTONUP, 0, 0);
(6)使对话框定时关闭:
在对话框窗口过程中,在对话框初始化消息分支中安装和设定一个定时器,在WM_TIMER消息分支中调用EndDialog。
(7)使一个菜单灰化或者重新使能:
设置:BOOL EnableMenuItem(HMENU hMenu, UINT uIDEnableItem, UINT uEnable);
查询:UINT GetMenuState(HMENU hMenu, UINT uId, UINT uFlags);
(8)获取对话框中的CheckBox是否被选中,设置CheckBox的状态:
查询:if ( IsDlgButtonChecked(hDlg, nIDButton) == BST_CHECKED) ...
设置:BOOL CheckDlgButton(HWND hDlg, int nIDButton, UINT uCheck);
(9)获取对话框中的文本输入框填写的数字:
UINT GetDlgItemInt(
HWND hDlg,
int nIDDlgItem,
BOOL * lpTranslated, //告知调用方是否转换成功
BOOL bSigned //是否需要考虑负号。
);
获取对话框控件的文本:
UINT GetDlgItemText(
HWND hDlg,
int nIDDlgItem,
LPTSTR lpString,
int nMaxCount
);
(10)如何处理对话框中的TreeView控件的选择节点变化,节点展开,节点折叠等事件:
对话框中的控件上发生事件时,通常会给它们的父窗口发送WM_NOTIFY消息。
LRESULT OnNotify(WORD uMsg, WORD wParam, LONG lParam, BOOL& bHandled)
{
LPNMHDR hdr=(LPNMHDR)lParam;
/*
code
Notification code. This member can be a control-specific notification code or it
can be one of the common notification codes.
*/
if(hdr->code==TVN_SELCHANGED)
{
//树节点发生改变
}
else if(hdr->code==TVN_ITEMEXPANDED)
{
LPNMTREEVIEW pnmtv = (LPNMTREEVIEW) lParam;
/*
#define TVE_COLLAPSE 0x0001
#define TVE_EXPAND 0x0002
#define TVE_TOGGLE 0x0003
*/
}
return 0;
}
(11)如果计算SYSTEMTIME加上一段时间后的SYSTEMTIME:
 Code_AddSeconds
typedef union
{
FILETIME fileTime; //文件时间
ULONGLONG uint64; //64-bit unsigned integer.
} UNION_FILETIME;
//为一个系统时间增加指定的秒数
BOOL AddSeconds(CONST SYSTEMTIME *lpSrcTime, SYSTEMTIME *lpDestTime, int seconds)
{
UNION_FILETIME uFileTime;
if(! SystemTimeToFileTime(lpSrcTime, (LPFILETIME)&uFileTime))
return FALSE;
//在文件时间上加上指定的分钟(转化为。。) 文件时间的单位0.0000001 sec, (10^(-7) sec)
//判断分钟的符号
if(seconds>0)
uFileTime.uint64 += UInt32x32To64(seconds,10000000);
else
uFileTime.uint64 -= UInt32x32To64(-seconds,10000000);
return FileTimeToSystemTime((FILETIME*)&uFileTime, lpDestTime);
}
(12)获取用户最近一次鼠标键盘输入到现在的时间(用户离开电脑的时间):
LASTINPUTINFO lastInput;
lastInput.cbSize = sizeof(lastInput); //important,donot forget!
GetLastInputInfo(&lastInput);
DWORD dwTicksSinceLastInput = GetTickCount() - lastInput.dwTime; (单位:毫秒)
备注:GetTickCount:获取自开机到现在的毫秒数。
(13)防止一个程序运行多个进程实例:通过创建mutex对象来判断当前是否已经有进程在运行;
 Code_Mutex
//打开mutex,参数:request full access,handle not inheritable,object name
hMutex = OpenMutex(MUTEX_ALL_ACCESS, FALSE, WINLOCK_MUTEX_NAME);
if(hMutex == NULL)
{
//说明系统中尚无本进程,进程退出后系统自动销毁该对象
//创建mutex,参数:no security descriptor,mutex not owned,object name
hMutex = CreateMutex(NULL, FALSE, WINLOCK_MUTEX_NAME);
}
else
{
//退出
exit(0);
}
(14)控制窗口是否在任务栏显示一个按钮:
a. 通过设定对话框的样式实现:
显示:创建一个没有OWNER的窗口,并设置WS_EX_APPWINDOW 扩展样式;
不显示:创建一个没有OWNER的窗口,并设置WS_EX_TOOLWINDOW 扩展样式;
b. 通过任务栏的COM对象来实现:
 Code_TaskbarList
HRESULT result;
ITaskbarList* pTaskbarList;
result = CoCreateInstance(CLSID_TaskbarList, NULL, CLSCTX_INPROC_SERVER,
IID_ITaskbarList, (void**)&pTaskbarList);
pTaskbarList->HrInit();
if(bShowInTaskbar) //在任务栏显示按钮
{
pTaskbarList->AddTab(hWnd);
}
else //在任务栏隐藏按钮
{
pTaskbarList->DeleteTab(hWnd);
}
pTaskbarList->Release();
(15)如何在通知栏(System Notify Area)放置一个图标(NotifyIcon):
 Code_Shell_NotifyIcon
NOTIFYICONDATA m_NotifyData;
HICON hicon = LoadIcon(hInstance,MAKEINTSOURCE(IDI_TRAYICONID));
m_NotifyData.cbSize = sizeof(NOTIFYICONDATA );//字节大小
m_NotifyData.hIcon = hicon;//图标句柄
m_NotifyData.hWnd = hwnd;//窗口句柄
m_NotifyData.uID = 0;//图标ID
#define WM_TRAYICON (WM_USER+120)
m_NotifyData.uCallbackMessage = WM_TRAYICON; //产生的消息
strcpy(m_NotifyData.szTip, "Icon's Tip Text");//鼠标在图标上悬停时的ToolTip Text
m_NotifyData.uFlags = NIF_MESSAGE|NIF_TIP|NIF_ICON; //设置要修改哪些属性
Shell_NotifyIcon(NIM_ADD, (PNOTIFYICONDATA)&m_NotifyData);//添加
(16)如何关闭或者注销计算机:
用户进程启动以后通常不具有关机权限,因此要关闭计算机,首先需要调整我们的进程的权限,获取关机权限:
 Code_ExitWindowsEx
//关闭计算机
LRESULT ShutDown()
{
HANDLE hToken;
TOKEN_PRIVILEGES tkp;
HANDLE hProcess;
int err;
LPVOID lpMsgBuf;
//当前进程的句柄
hProcess=GetCurrentProcess();
//获取调整权限和查询权限的token
if (OpenProcessToken(hProcess, TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
{
// open and check the privileges for to perform the actions
// #define SE_SHUTDOWN_NAME TEXT("SeShutdownPrivilege")
LookupPrivilegeValue(NULL, SE_SHUTDOWN_NAME, &tkp.Privileges[0].Luid);
tkp.PrivilegeCount = 1;
if(AdjustTokenPrivileges(hToken, FALSE, &tkp, 0, (PTOKEN_PRIVILEGES)NULL, 0))
{
//adjust the privilege to perform the action,
//现在我们的进程已经具有了权限!
//关机
ExitWindowsEx(EWX_SHUTDOWN|EWX_POWEROFF|EWX_FORCE,0);
//注销
//ExitWindowsEx(EWX_LOGOFF|EWX_FORCE,0);
//重启
//ExitWindowsEx(EWX_REBOOT|EWX_FORCE,0);
}
else
MessageBox(NULL,"无法关机","HanTing Hotels",MB_OK);
}
err = GetLastError();
if(err)
{
// 将错误代码转换成文本
FormatMessage(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM, //dwFlags
NULL, //lpSource
err, //dwMessageId
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), //dwLanguageId
(LPTSTR)&lpMsgBuf, //lpBuffer
0, //nSize
NULL //Arguments
);
MessageBox(NULL,(LPTSTR)lpMsgBuf, "", MB_OK|MB_ICONERROR);
LocalFree(lpMsgBuf);
}
else
{
//没有错误出现, 结束自身进程
PostQuitMessage(0);
exit(0);
}
return 1;
}
(17)如何在注册表中写入一个键值:
 Code_RegSetValueEx
//--------------------------
// write a keyvalue
//--------------------------
HKEY hKey;
LONG result;
DWORD dwType=REG_SZ;//C字符串
char lpData[128];
DWORD cbData=strlen(value)+1;//设置字符串时,size必须要包含结尾的\0
if (RegOpenKey(HKEY_LOCAL_MACHINE, "keyName", &hKey) != ERROR_SUCCESS)
{
//如果键不存在,则创建它
if (RegCreateKey(HKEY_LOCAL_MACHINE, "keyName", &hKey) != ERROR_SUCCESS)
return FALSE;
}
result = RegSetValueEx(hKey, "valueName", 0, dwType, (VOID*)lpData, cbData);
RegCloseKey(hKey);
//--------------------------
// query a keyvalue
//--------------------------
if (RegOpenKey(HKEY_LOCAL_MACHINE, "keyName", &hKey) != ERROR_SUCCESS)
{
return FALSE;
}
result = RegQueryValueEx(hKey, "valueName", NULL, &dwType, lpData, &cbData);
RegCloseKey(hKey);
BalloonTip通常出现在位于屏幕右下角的Tray(成为通知栏,或系统托盘)的Icon上,比如用户插拔USB设备时的气泡提示。该函数位于Shell32中,是通过调用下面这个API函数实现的, BOOL Shell_NotifyIcon( DWORD dwMessage, PNOTIFYICONDATA lpdata ); 该函数用于控制对TrayIcon进行控制,可以弹出BalloonTip。然后却只能局限于Tray位置。而在QQ的聊天对话框中,我们发现如果不输入内容而试图发送消息时,就会在按钮上弹出这样的Tip。为此,我在C#中写了一个类似的窗口,可以在任意位置浮出,效果如下:
这是测试程序的截图。在实现时,略微参考了codeproject上的仿MSN浮出窗口的代码。其涉及的主要麻烦是,为了提供足够灵活的接口,应该如何处理窗口上各个元素布局。为了简单期间,这里仅仅提供了“箭头”位于右下角的布局方式。(很显然该箭头一共可以有8个停靠位置)。
(1)在弹出时,不夺走其他窗口焦点。为此使用了API的ShowWindow函数: ShowWindow(this.Handle, SW_SHOWNOACTIVATE);
(2)窗体上一共包含4项主要内容:图标,标题,内容文本,关闭按钮。用户可以配置他们,例如选择图标(可以由外部设置或者使用内置图标),是否显示关闭按钮,是否自动关闭(如果把TimeoutMilliSeconds属性设为负数则不会自动关闭,只能通过CloseButton关闭),各种颜色,字体等。另提供一个BalloonClick事件,通知外部用户点击事件。
(3)绘制CloseButton的位图时,由于图中具有透明色,因此需要在绘制时指定透明色,相当于蒙版的作用。否则透明色会被绘制到窗体上导致窗体透明那个。
(4)弹出该窗口,主要使用ShowAt()函数。里面需要一些参数。如果需要进一步设置其他属性,可以单独进行设置。
源代码的下载链接:
http://www.cnblogs.com/Files/hoodlum1980/JDL.UILib_BalloonTip_VS7.rar
编译器提示:warning treated as error,然后某个文件未生成,build中断。
这是因为项目选项中把warning级别设置为了较高的级别导致的,某些warning被当作error,从而不能生成。
解决方法是在解决方案窗口中选中项目,右键点击project,选择“属性”,在弹出的对话框中,选择左侧TreeView中的“Configuration Properties”->“C/C++l”->“General”节点,在右侧有“Treat Warnings As Errors”(将警告当作错误对待)选项。我们把该项该为NO即可。即把相应命令行的"/WX"改为"/W"。
相应的编译选项如下:(参考MSDN)
/w 禁止所有警告
/Wn 指定显示的最高等级警告。有效等级是0~4。0级禁止所有警告。4级显示所有警告。
/Wall 使能所有警告。
/WX 视所有警告为错误。
/wln 将某个指定警告视为某个级别。第一个参数是新的级别,第二个参数是警告号码。例如/w14326 使 C4326成为1级警告。
/wdn 禁止某特定警告。n是警告号。例如, /wd4326 禁止C4326警告。
/wen 将某个指定警告视为错误。n是警告号。例如, /we4326 把 C4326 视为错误。
/won 某警告仅仅报告一次。例如:wo4326使C4326仅仅报告一次。
摘要:  摘要: 最近有感于部分网友对高斯模糊滤镜的研究,本文将对高斯模糊中半径值的含义以及高斯模糊模板尺寸的疑惑做出总结和解答。 阅读全文
摘要: 在上一篇文章中,我们介绍了开发Photoshop滤镜插件最基本的一些概念和基础。Ps为了满足插件的应用需求,同时也给插件提供了大量的回调函数(或服务)。例如,滤镜可以在一次调用后,保存最近一次用户设置的参数,并应用到下次调用或显示UI。这就是通过Ps的回调函数完成的。这一篇文章我们将讲解最重要的一些Ps回调函数。了解本文之后,我们将能够使用回调函数,完成例如存储我们的滤镜参数等必要的工作。本篇文章将比第一篇复杂和深入的多,但同时从这篇文章我们也可以一窥PS内部的秘密:缜密的系统设计,完善的接口以及复杂的工作机制。 阅读全文
摘要: 在flyweight模式,指的是具有大量的轻量级对象,我们为这些对象建立一个实体对象,其他则为“虚像”或者称为对该实体的一种“引用”。在我从前的项目中,电力系统的矢量图中,有大量设备,同种类型设备采用一种符号绘制,称为图元。这里就属于一中flyweight模式应用。...... 阅读全文
摘要: Photoshop是数字图像处理的杰出软件。他允许第三方以插件形式扩展功能。本文讲解用户最为熟悉的Photoshop滤镜插件的开发,以一个水滴效果滤镜为实例,主要介绍滤镜插件的开发流程,讨论了一些比较重要的相关技术细节问题。 阅读全文
题目链接: http://acm.zju.edu.cn/show_problem.php?pid=1146
这是一道用于把数字显示成LCD样子的题目,输入每一行有两个整数,第一个整数n表示笔画大小,第二个是需要显示成LCD样子的数字。这道题没有什么太难的,但是却让我在输出格式上卡住了,始终是Presentation Error,让我百思不得其解。后来我才发现原来是因为题目叙述的不够精确,使我没有准确理解输出格式的要求,导致我每一行都多输出了一个结尾空格。找到问题后,当然立刻就AC了。代码也不具备什么含量。
由于没有什么难度的地方,并且我也加了一点点注释,所以就没什么可做更多叙述的了。这只是一道比较简单的题目,但是输出格式一定要正确理解。
 ZOL 1146 CODE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void PrintVer(int,int,char*);
void PrintHor(int,int,char*);
void PrintNumbers(int,char*);
/*LCD的字型码*/
/* -0-
1 2
-3-
4 5
-6-
*/
int LCDCODES[10][7]=
{
{1,1,1,0,1,1,1},/*0*/
{0,0,1,0,0,1,0},/*1*/
{1,0,1,1,1,0,1},/*2*/
{1,0,1,1,0,1,1},/*3*/
{0,1,1,1,0,1,0},/*4*/
{1,1,0,1,0,1,1},/*5*/
{1,1,0,1,1,1,1},/*6*/
{1,0,1,0,0,1,0},/*7*/
{1,1,1,1,1,1,1},/*8*/
{1,1,1,1,0,1,1} /*9*/
};
char c_ver='|';
char c_hor='-';
/* 两个相邻字母中间隔了一个空列 ,因此平均每个字母占据n+3列,最后一列为间隔*/
/*打印竖直笔画,n-笔画长度,section=1或者4,numbers-数字字符串*/
void PrintVer(int n,int section,char *numbers)
{
char *line;
int i,len=(n+3)*strlen(numbers);
line=(char*)malloc(len+1);
if(line==NULL)
return;
memset(line,' ',len);
for(i=0;i<strlen(numbers);i++)
{
if(LCDCODES[*(numbers+i)-'0'][section])
*(line+(n+3)*i)=c_ver;
if(LCDCODES[*(numbers+i)-'0'][section+1])
*(line+(n+3)*i+(n+1))=c_ver;
}
/*检查最后一个字符,如果没有笔画,要使\0前移2格!*/
line[len-1]='\0';
/*打印n行*/
for(i=0;i<n;i++)
printf("%s\n",line);
free(line);
}
/*打印水平笔画,n-笔画长度,section=0,3,or 6,numbers-数字字符串*/
void PrintHor(int n,int section,char *numbers)
{
char *line;
int i,len=(n+3)*strlen(numbers);
line=(char*)malloc(len+1);
if(line==NULL)
return;
memset(line,' ',len);
for(i=0;i<strlen(numbers);i++)
{
if(LCDCODES[*(numbers+i)-'0'][section])
memset((line+(n+3)*i+1),c_hor,n);
}
/*注意最后一个数字后面无需空格了!所以多缩进一个位置*/
line[len-1]='\0';
/*打印1行*/
printf("%s\n",line);
free(line);
}
/* Print A set of Numbers : "23456" e.g. */
void PrintNumbers(int n,char *numbers)
{
PrintHor(n,0,numbers); /* ---- */
PrintVer(n,1,numbers); /* | | */
PrintHor(n,3,numbers); /* ---- */
PrintVer(n,4,numbers); /* | | */
PrintHor(n,6,numbers); /* ---- */
}
int main()
{
char line[20];
char *str1,*numbers,*delim=" ";
int n;
do
{
gets(line);
str1=strtok(line,delim);
numbers=strtok(NULL,delim);
n=atoi(str1);
if(n<=0)
break;
PrintNumbers(n,numbers);
/*每两行数字之间有一个空行*/
printf("\n");
}
while(1);
}
/*--------------------------------------------------
打印后正确的输出格式应该类似下面这样:(@表示行尾)
- - - - @
| | | | | | | | @
- - - - @
| | | | | | |@
- - - - @
----------------------------------------------------*/
这道题目:http://acm.zju.edu.cn/show_problem.php?pid=1113 的大意如下:根据公式
e=1/0!+1/1!+1/2!+1/3!+...+1/n!
计算e。要求输出需要的小数位数(9位)。
题目很直观,最直观的想法可能是一个计算n!的函数,然后一个从0到n的循环,累加所有的小数。不过这样做显然会有很多冗余计算。为了避免这种情况,显然,
假设a[n]表示1/n!,e[n]表示e,则
a[n+1]=a[n]/(n+1);
e[n+1]=e[n]+a[n+1];
根据这个等式,我们可以写出解答方法。由于题目中要求小数精度,也许随着计算机位数的增长,double能够满足精度。但是在16位的TC下似乎是无法满足的,所以考虑用大数方法处理小数。即使用一个数组a[]表示小数部分。(这里假设小数具有0.********的形式),按照这个处理方法的惯例,我们规定,
a[0]表示小数的数据位数,a[i] (i>0)中存放第i位小数的数字。例如PI 3.1415926,它的小数部分可以用下面的数组表示:
{7,1,4,1,5,9,2,6,0,0,0,........};
那么这个小数就可以表示成
a[1]*10^(-1)+a[2]*10^(-2)+...+a[n]*10^(-n)
由于
a * 10^(-i) + (10*b + c) * 10^(-(i+1)) = (a+b) * 10^(-i) + c * 10 ^(-(i+1))
所以,相邻位之间以此规则进位或者退位以及规整。
根据上面的迭代式子,我们看到至少两种小数运算,一个是小数除以整数,一个是两个小数相加。在这个题目中实际上精度不高,因此对代码执行效率的要求可以降低,但是为了通用,我还是保持该算法的惯用风格。
下面,给出一个小数除以一个整数的代码,由于一个已经规整过的小数这样运算的时候,不可能出现大于10的结果,所以这时我们无需对结果进行规整处理。
 一个小数除以一个整数
#define DISLEN 9 /*最多小数位数*/
#define TOTLEN 13 /*总长度,即显示小数位数+冗余的小数位数*/
/*计算一个0.--的小数除以一个整数后的小数。*/
void Devide(int a[],int n)
{
int i,temp;/*余数*/
for(i=1; i<=a[0] && i<(TOTLEN-1); i++)
{
temp=a[i]%n; /*先保存余数!!!不能先改变a[i],切记!*/
a[i]=a[i]/n;
a[i+1]+=temp*10;
if(i==a[0] && temp!=0)
a[0]=i+1;
}
/*对最后一位进行除法运算*/
a[TOTLEN-1]/=n;
}
由于结果需要四舍五入,所以实际上我给出了一些冗余位,以保持四舍五入时候的结果正确。
下面是两个小数相加的运算代码,即a=a+b,结果是a被改变为两者的和,b不发生变化。在a的基础上改动,可以使我们节省空间。相对而言,大数相加减的代码是最为简单直观的。
 两个小数相加
/*注意两个正小数相加,他们的最大位数只可能减少,不可能增加!*/
void Add(int a[],int b[])
{
int i;
a[0]=MAX(a[0],b[0]);
for(i=1;i<=a[0];i++)
{
a[i]+=b[i];
}
/*规整小数*/
formular(a);
}
/*加法后的规整小数*/
void formular(int a[])
{
int i;
for(i=a[0];i>1;i--)
{
a[i-1]+=a[i]/10;
a[i]=a[i]%10;
}
a[1]=a[1]%10;
/*当最后一位为0时,位数递减(缩减位数)*/
while(!a[a[0]] && a[0])
{
a[0]--;
}
}
注意,两个小数相加以后,可能产生结果在某些位大于10,因此这时需要一次规整运算。
最后,我们还需要对数组进行一次扫描,输出四舍五入后的结果:由于我们的目的是计算e,因此整数部分是确定的“2.”。
 输出四舍五入后的结果值
/*输出e的小数表示,用2.开始,e[]中是小数部分*/
void OutputE(int a[])
{
/*len表示四舍五入后的小数长度,flag表示是否进位!*/
int i,len,flag;
printf("2.");
/*对最后一位进行四舍五入!如果位数不大于DISLEN说明无需四舍五入*/
len=DISLEN+1;
if(a[len]>4)
{
flag=1;
while(a[--len]==9);
}
else
{
flag=0;
while(a[--len]==0);
}
/*打印前几个字符*/
for(i=1;i<len;i++)
printf("%d",a[i]);
/*根据是否进位,打印最后一个数字*/
if(len>0)
printf("%d",flag? (a[len]+1):a[len]);
for(i=len+1;i<=DISLEN;i++)
printf("0");
printf("\n");
}
最后我们给出迭代部分的代码,由于使用迭代式,因此下一步的计算建立在前一步的计算结果的基础上,这样就避免了冗余计算。
 迭代E的结果
int e[TOTLEN],a[TOTLEN];
/* e2=2.5, a2=0.5 */
e[0]=a[0]=1;
e[1]=a[1]=5;
for(j=3;j<=9;j++)
{
Devide(a,j); /*a[n+1]=a[n]/(n+1);*/
Add(e,a); /*e[n+1]=e[n]+a[n+1];*/
printf("%d ",j);
OutputE(e);
}
当我们想要输出80位小数时,结果如下(显然可以直接运算的内部数据类型是无法达到这样精度的):
2.71828182845904523536028747135266249775724709369995957496696762772407663035354759
首先我们来看一下这个问题的提出,来自于一个网友的提问:
http://bbs.bccn.net/thread-200774-1-1.html
----------------------------------------------------------------------------------------------------------
求教大家,简单问题,但为什么是这样的结果?(vc6.0)
很简单的程序
void main()
{
int i=8;
printf("%d,%d,%d,%d\n", ++i, --i, i++, i--);
}
但是结果为(8 7 8 8)无论是从左到右顺序求值还是从右到左顺序求值都不应该是这个结果吧?
我觉得从左到右应该是(9 8 8 9 )从右到左是(8 7 7 8),
是我的错还是编译器的原因?如果是从右到左顺序求值,为什么结果不是(8 7 7 8)而是(8 7 8 8)
请大家指点一下!
[ 本帖最后由 默默无纹 于 2008-2-24 21:04 编辑 ]
------------------------------------------- |