Python爬虫自动下载Discuz论坛附件。

2010-05-15 发表在 程序开发 | 标签 , , | 查看 714 次 | 5 条评论

因工作需要,要定期收集卡饭论坛的病毒样本板块的病毒样本,所以就考虑用 Python做个爬虫,然后自动下载附件。
核心功能有3个:
1· 登录
2· 伪造cookie保持session
3. 下载样本
阅读全文 »

[驱动开发学习] DDK与WDK

2009-11-21 发表在 程序开发 | 查看 78 次 | 评论

最近尝试去了解WINDOWS下的驱动开发,现在总结一下最近看到的资料。

  1.首先,先从基础的东西说起,开发WINDOWS下的驱动程序,需要一个专门的开发包,如:开发JAVA程序,我们可能需要一个JDK,开发 WINDOWS应用程序,我们需要WINDOWS的SDK,现在开发WINDOWS下的驱动程序,我们需要一个DDK/WDK。

  2.DDK(Driver Developer Kit)和WDK(Windows Driver Kit)的区别:

  这个要说说驱动相关的一些历史:

  1).95/98/ME下,驱动模型为:Vxd,相关资料可以看《编程高手箴言》的前几个章节,里面有很详细的介绍,虽然这个东西已经过时,但大概看看还是会增长见识的。

  2).2000/XP/2003下,Windows采用WDM驱动模型(Windows Driver Model),开发2000/XP/2003的驱动开发包为:DDK。

  3).Vista及以后版本,采用了WDF驱动模型(Windows Driver Foudation),对应的开发包:WDK。

其实WDK可以看做是DDK的升级版本,现在一般的WDK是包含以前DDK相关的功能,现在XP下也可以用WDK开发驱动,WDK能编译出2000-2008的各种驱动。

  3.Vxd驱动文件扩展名为:.vxd。

   WDM和WDF驱动文件扩展名为:.sys。

参考资料:

1.http://topic.csdn.net/u/20071225/13/bbb7eeba-7abf-483d-8724-abb76aa4e22c.html

2.http://topic.csdn.net/u/20090104/16/9d09f896-fd4b-4b6b-807c-990ddc63c35b.html

3.http://topic.csdn.net/u/20080204/19/2ca8d266-f384-4d8f-865c-c16a33aa97b0.html

VC实现美化界面的几种方式。

2009-07-04 发表在 程序开发 | 查看 50 次 | 评论

最近由于做杀毒手的缘故,在这方面小有点研究,所以概括一下。

1·最简单最省事儿的,当然是使用第三方的皮肤控件。
这些控件按使用方法、效率、美化的控件数量分优劣,所以或多或少给你的项目带来了未知风险。

2·HOOK。一般是是HOOK窗体过程函数,在这个函数里根据消息类型做判断。
其实方法1大部分也是使用了HOOK,不过由于1通常都是直接拿来dll,而2则是自己写,所以这里就分为两个方法。

3·在项目里独立出一个UI模块,其他各个模块的控件都是UI模块生成。UI模块的控件类对其他模块是透明的。
这也就是闪电杀毒手使用的方法。这种方法的优点是:界面统一规范;缺点是:麻烦……

4·也是有一个独立的UI模块,UI里面重写了各种控件。不过这些控件类对其他模块是开放的。其他模块在声明控件变量的时候,直接使用UI模块的控件类,即包含那些控件类的头文件。但是还能对控件进行可视化操作,即直接拖放控件,然后使用DDX_Control(pDX, IDC_MYBUTTON, m_btn1);进行子类化即可实现界面美化。我目前觉得这种方法不错,优点是:简单异操作;缺点可能就是编译的体积较大。

利用SetUnhandledExceptionFilter对Release程序进行异常处理。

2009-07-02 发表在 程序开发 | 查看 170 次 | 1 条评论

闪电杀毒手2.5发布出去有一段时间了,最近收到几个Bug反馈。
通常发布出去的Release产品,如果遇到没有预料到的异常,通常都会弹出Windows默认的错误窗口Windows Error Reporter.
这时用户就傻了:交互不友好;RD也傻了:不知道异常的具体信息。

SetUnhandledExceptionFilter可以设置在WER弹出之前的最后一次异常处理的机会。所以只要设置好我们的异常处理函数就可以捕获到Unhandled Exception。

[Code]
SetUnhandledExceptionFilter(CleanToolExceptionFun);
LONG WINAPI CleanToolExceptionFun(struct _EXCEPTION_POINTERS* ExceptionInfo)
{

}
[/Code]

在这个函数里你可以做以下几件事:
1·获得异常的ExceptionRecord和Context
2·获得异常编号
3·获得异常的模块
4·获得堆栈的信息
5·程序继续执行还是退出

1和2都好办,直接通过传递进来的参数就可以获得:
[Code]
ExceptionInfo->ExceptionRecord->ExceptionAddress;
[/Code]

3通常的做法是,通过ExceptionRecord获得异常的内存地址,调用VirtualQuery获得Handle,再通过GetModuleFileName获得出现异常的文件路径。
[code]
MEMORY_BASIC_INFORMATION mbi = {0};
if (FALSE == ::VirtualQuery( addr, &mbi, sizeof(mbi) ) ) return;
UINT_PTR h_module = (UINT_PTR)mbi.AllocationBase;
::GetModuleFileNameW((HMODULE)h_module, sz_module, len);
return;
[/code]

4的做法是通过StackWalk64函数便利堆栈。
[code]
StackWalk64(IMAGE_FILE_MACHINE_I386,hCurrentProcess,hCurrentThread,&sStackFrame,pContext,0,0,0,0)
[/code]

5是通过函数的返回值来确定的。

在实现的过程遇到几个好玩的问题:
1·如果exe调用dll,dll中要使用AFX_MANAGE_STATE(AfxGetStaticModuleState())之后,dll的资源才能被访问到;但是如果dll调用exe,则必须使用AFX_MANAGE_STATE(AfxGetAppModuleState())。

2·StackWalk64的问题:在Release版本下,Stackwalk64获得的堆栈信息不完整,具体表现为:如果异常是由MFC的模块抛出的,那么获得的堆栈将缺少栈最上方我们自己的模块的信息。比如A->B->MFC,则获得的栈为(至顶向下):MFC->A。但是在Debug的版本下不存在这个问题。为此我还特意用OD调试了Release版本,发现堆栈是完整的,那么只能认为是StackWalk的问题了。

3·在便利异常的模块的节表的时候,Release版本下程序偶尔会莫名其妙地退出。

4·Release编译的优化问题:如果我直接写int i = 5/0,编译时会直接报错。如果我写成:
int i = 0;
AfxMessageBox(L"%d", 5 /i),编译就可以通过。汗……

一个野指针引发的错误。

2009-05-19 发表在 程序开发 | 查看 43 次 | 评论

杀毒手的升级模块最近有个bug,导致小内存用户在关闭杀毒手的时候会报错。

debug之后,发现原因是,升级模块的析构函数里销毁了一个野指针。
由于升级模块的对象创建使用的是单例模式,之所以这么做是因为,该模块里有多个线程,所以会有成员函数和静态函数,而静态函数中也用到了该模块的对象,为了控制该对象的创建,所以才采用单例模式。

而单例模式需要生成和销毁都被统一管理。调用者无需介入。

而这个bug就是因为在析构函数里销毁了这个对象。而这个对象已经被单例给销毁了。
但是,指针并没指向NULL,仍指向原先的区域。
如果这个区域过一段时间又被存放了新的数据,delete的时候就会报错。

所以这个问题会发生在小内存的机器上,因为小内存的机器上,一块内存被重复利用的几率非常高。
因此,为了防止内存泄漏,及时销毁对象是必须的,但是别忘了,销毁之后要将指针赋NULL,以提高程序的健壮性。

使用VC监听某个网页的变化。

2009-05-16 发表在 程序开发 | 查看 46 次 | 评论

由于夜班的需要,我们要经常看siebel上是否有新案件。
上一个工具(http://www.colordancer.net/blog/article.asp?id=206)采用的是根据email和病毒样本数来判断的,但是这样难免有疏漏,所以我决定从siebel这个“祸源”上解决问题。

通常判断一个网页有没有变化,做法是直接拿到html,然后做dom分析。
但是siebel采用的是全站js和applet,无法拿到html,所以,这里的做法是判断某个范围内坐标的像素的RGB值。

做法概括起来说如下:
1·新建MFC对话框程序,插入ActiveX控件:Microsoft Web浏览器
2·是浏览器默认打开我们指定的页面,然后定时刷新、通过GetPixel获取某个位置的像素RGB值

需要注意的事项如下:
1·对ActiveX控件直接获得DC是有问题的,返回像素的RGB总是-1,所以我先把它放到个对话框里,再把这个对话框B放到主程序的对话框A上,然后对B取DC来GetPixel
2·GetPixel必须要让当前窗口可见,不然返回值不正确或者获得失败(-1)
3·如果要判断的网页含有post数据,则不能用刷新,不然会弹出一个对话框,问你是不是要重新发送数据,所以我这里没有使用Refresh函数,而是重新Navgate相同的URL

截图如下:

这中做法其实也有通用性,只要把判断的坐标和像素RGB做成可变,那就能适用到不少场合了。

使用C#监听Outlook特定新邮件。

2009-05-12 发表在 程序开发 | 查看 111 次 | 2 条评论

这个月开始要上夜班,由于做事是on email的,所以部门以前的做法是,隔一段时间起床刷一下邮件。我受不了,所以做了个工具,用来监听outlook的新邮件,并做声音提示。

其实关键代码没有几行,难度在于这些函数在MSDN里基本找不到。术语上叫做基于Office的Add-in的开发。
但是这里我并没有实现成插件,只是截获了Outlook的NewMailEx事件,然后通过回调函数做判断。

核心代码如下:
[code]
public void _tMonitorOutLook()
{
ApplicationClass outLookApp = new ApplicationClass();
outLookApp.NewMailEx += new ApplicationEvents_11_NewMailExEventHandler(outLookApp_NewMailEx);
MessageBox.Show("开始监听Outlook邮件!");
while (true)
{
Thread.Sleep(10);
}
}

private static void outLookApp_NewMailEx(string EntryIDCollection)
{
ApplicationClass outLookApp = new ApplicationClass();
NameSpace outLookNS = outLookApp.GetNamespace("MAPI");
MAPIFolder outLookFolder = outLookNS.GetDefaultFolder(OlDefaultFolders.olFolderInbox);
string storeID = outLookFolder.StoreID;

MailItem mail = (MailItem)outLookNS.GetItemFromID(EntryIDCollection, storeID);

//判断标题
Regex rx = new Regex(s_strRegex);
if (rx.IsMatch(mail.Subject))
NewPspCaseArrived("有新邮件到达:" + mail.Subject);

}
[/code]

原理如下:
1·修改NewMailEx的回调函数,指向我们自己的函数
2·NewMailEx会传递一个EntryID过来,作为参数1,我们再获得收件箱的StoreID,作为参数2,然后调用GetItemFromID,就可以获得新邮件的实例
3·接下来就可以做自己想做的事情了

我的工具截图如下:

如果你感兴趣,可以从以下链接下载:
点我下载

当然,如果你使用的是Outlook,而且你还会写一点正则表达式,那么你也可以使用这个工具监听你的邮件了。只要你把监听文件夹改成一个不想关的文件夹就可以了。不过别忘了,需要.Net Framework 2.0(+)。

[转]VC多语言程序实现。

2009-03-25 发表在 程序开发 | 查看 64 次 | 评论

最近杀毒手要重新设计多语言版的实现方式,找了点资料,差不多就以下三种方式了,个人也觉得第3种最好。

—————————-转载的分割线—————————-

作为软件开发作者,经常希望自己的软件能够被更多更广泛的用户使用,其中有可能包括外国用户,如果软件界面能够支持用户本国语言当然会有利于软件的推广,所以很多作者都想实现自己的软件多语言支持[truncate],以下根据自己的经验对集中实现方式做一下简单的对比(针对VC开发者)

1. 修改资源文件
该方法是最早实现多语言版本的方式, VC支持多钟语言资源,根据用户喜好动态调入特定资源库文件,该方式实现简单,对程序本身的影响不大,只需要写好调入资源库代码就OK,而且该方法可以根据不同语言习惯调整窗口布局方式。不过此方法也有缺点,每次修改资源都需要重新编译库文件才能使用。

2. 使用类似INI文件的语言文件方式
这种方法需要定义与窗口和窗口上控件对象对应的字符串,在更换语言后加载新的语言文件即可。该方法优点显而易见,翻译途径多样化,任何人如果完成翻译,只要把翻译文件放在指定目录就可以被程序搜索到并且可以直接使用,相对而言灵活方便。 但此方法不能顾及到不同语言的语言习惯下的窗口布局,只能是针对字句的翻译。

3. 使用XML方式
用XML定义窗口(对话框) 的布局以及文本信息,翻译方便的同时能够根据不同语言调整布局,非常方便使用,不过对程序的解析部分要求较高。

个人比较喜欢使用第二种方式,也就是才用INI 文件的方式实现,一般做法是对不同功能进行分类作为section, 功能内的窗口控件对象的编号作为key,比如
中文版
[1000]
1001=确定

英文版
[1000]
1001=OK

其中1000就是section,一般为对话框或者功能分类代码, 1001作为该分类的对象也就是Key,上例中是作为一个按钮控件

然后使用Win32 API GetPrivateProfileString()读取相应的字符窜 替换对象文本即可。

vc++中各种字符串。

2008-12-31 发表在 程序开发 | 查看 24 次 | 评论

CString ,BSTR ,LPCTSTR之间关系和区别

CString是一个动态TCHAR数组,BSTR是一种专有格式的字符串(需要用系统提供的函数来操纵,LPCTSTR只是一个常量的TCHAR指针。

CString 是一个完全独立的类,动态的TCHAR数组,封装了 + 等操作符和字符串操作方法。
typedef OLECHAR FAR* BSTR;
typedef const char * LPCTSTR;

vc++中各种字符串的表示法

首先char* 是指向ANSI字符数组的指针,其中每个字符占据8位(有效数据是除掉最高位的其他7位),这里保持了与传统的C,C++的兼容。

LP的含义是长指针(long pointer)。LPSTR是一个指向以‘/0’结尾的ANSI字符数组的指针,与char*可以互换使用,在win32中较多地使用LPSTR。
而LPCSTR中增加的‘C’的含义是“CONSTANT”(常量),表明这种数据类型的实例不能被使用它的API函数改变,除此之外,它与LPSTR是等同的。
1.LP表示长指针,在win16下有长指针(LP)和短指针(P)的区别,而在win32下是没有区别的,都是32位.所以这里的LP和P是等价的.
2.C表示const
3.T是什么东西呢,我们知道TCHAR在采用Unicode方式编译时是wchar_t,在普通时编译成char.

为了满足程序代码国际化的需要,业界推出了Unicode标准,它提供了一种简单和一致的表达字符串的方法,所有字符中的字节都是16位的值,其数量也可以满足差不多世界上所有书面语言字符的编码需求,开发程序时使用Unicode(类型为wchar_t)是一种被鼓励的做法。

LPWSTR与LPCWSTR由此产生,它们的含义类似于LPSTR与LPCSTR,只是字符数据是16位的wchar_t而不是char。

然后为了实现两种编码的通用,提出了TCHAR的定义:
如果定义_UNICODE,声明如下:
typedef wchar_t TCHAR;
如果没有定义_UNICODE,则声明如下:
typedef char TCHAR;

LPTSTR和LPCTSTR中的含义就是每个字符是这样的TCHAR。

CString类中的字符就是被声明为TCHAR类型的,它提供了一个封装好的类供用户方便地使用。

LPCTSTR:
#ifdef _UNICODE
typedef const wchar_t * LPCTSTR;
#else
typedef const char * LPCTSTR;
#endif

VC常用数据类型使用转换详解

先定义一些常见类型变量借以说明
int i = 100;
long l = 2001;
float f=300.2;
double d=12345.119;
char username[]="女侠程佩君";
char temp[200];
char *buf;
CString str;
_variant_t v1;
_bstr_t v2;

一、其它数据类型转换为字符串

短整型(int)
itoa(i,temp,10);//将i转换为字符串放入temp中,最后一个数字表示十进制
itoa(i,temp,2); //按二进制方式转换
长整型(long)
ltoa(l,temp,10);

二、从其它包含字符串的变量中获取指向该字符串的指针

CString变量
str = "2008北京奥运";
buf = (LPSTR)(LPCTSTR)str;
BSTR类型的_variant_t变量
v1 = (_bstr_t)"程序员";
buf = _com_util::ConvertBSTRToString((_bstr_t)v1);

三、字符串转换为其它数据类型
strcpy(temp,"123");

短整型(int)
i = atoi(temp);
长整型(long)
l = atol(temp);
浮点(double)
d = atof(temp);

四、其它数据类型转换到CString

使用CString的成员函数Format来转换,例如:

整数(int)
str.Format("%d",i);
浮点数(float)
str.Format("%f",i);
字符串指针(char *)等已经被CString构造函数支持的数据类型可以直接赋值
str = username;

五、BSTR、_bstr_t与CComBSTR

CComBSTR、_bstr_t是对BSTR的封装,BSTR是指向字符串的32位指针。
char *转换到BSTR可以这样: BSTR b=_com_util::ConvertStringToBSTR("数据");//使用前需要加上头文件comutil.h
反之可以使用char *p=_com_util::ConvertBSTRToString(b);

六、VARIANT 、_variant_t 与 COleVariant

VARIANT的结构可以参考头文件VC98/Include/OAIDL.H中关于结构体tagVARIANT的定义。
对于VARIANT变量的赋值:首先给vt成员赋值,指明数据类型,再对联合结构中相同数据类型的变量赋值,举个例子:
VARIANT va;
int a=2001;
va.vt=VT_I4; //指明整型数据
va.lVal=a; //赋值

对于不马上赋值的VARIANT,最好先用Void VariantInit(VARIANTARG FAR* pvarg);进行初始化,其本质是将vt设置为VT_EMPTY,下表我们列举vt与常用数据的对应关系:

unsigned char bVal; VT_UI1
short iVal; VT_I2
long lVal; VT_I4
float fltVal; VT_R4
double dblVal; VT_R8
VARIANT_BOOL boolVal; VT_BOOL
SCODE scode; VT_ERROR
CY cyVal; VT_CY
DATE date; VT_DATE
BSTR bstrVal; VT_BSTR
IUnknown FAR* punkVal; VT_UNKNOWN
IDispatch FAR* pdispVal; VT_DISPATCH
SAFEARRAY FAR* parray; VT_ARRAY|*
unsigned char FAR* pbVal; VT_BYREF|VT_UI1
short FAR* piVal; VT_BYREF|VT_I2
long FAR* plVal; VT_BYREF|VT_I4
float FAR* pfltVal; VT_BYREF|VT_R4
double FAR* pdblVal; VT_BYREF|VT_R8
VARIANT_BOOL FAR* pboolVal; VT_BYREF|VT_BOOL
SCODE FAR* pscode; VT_BYREF|VT_ERROR
CY FAR* pcyVal; VT_BYREF|VT_CY
DATE FAR* pdate; VT_BYREF|VT_DATE
BSTR FAR* pbstrVal; VT_BYREF|VT_BSTR
IUnknown FAR* FAR* ppunkVal; VT_BYREF|VT_UNKNOWN
IDispatch FAR* FAR* ppdispVal; VT_BYREF|VT_DISPATCH
SAFEARRAY FAR* FAR* pparray; VT_ARRAY|*
VARIANT FAR* pvarVal; VT_BYREF|VT_VARIANT
void FAR* byref; VT_BYREF

_variant_t是VARIANT的封装类,其赋值可以使用强制类型转换,其构造函数会自动处理这些数据类型。
例如:
long l=222;
ing i=100;
_variant_t lVal(l);
lVal = (long)i;

COleVariant的使用与_variant_t的方法基本一样,请参考如下例子:
COleVariant v3 = "字符串", v4 = (long)1999;
CString str =(BSTR)v3.pbstrVal;
long i = v4.lVal;

七、其它

对消息的处理中我们经常需要将WPARAM或LPARAM等32位数据(DWORD)分解成两个16位数据(WORD),例如:
LPARAM lParam;
WORD loValue = LOWORD(lParam);//取低16位
WORD hiValue = HIWORD(lParam);//取高16位
对于16位的数据(WORD)我们可以用同样的方法分解成高低两个8位数据(BYTE),例如:
WORD wValue;
BYTE loValue = LOBYTE(wValue);//取低8位
BYTE hiValue = HIBYTE(wValue);//取高8位

如何将CString类型的变量赋给char*类型的变量
1、GetBuffer函数:
使用CString::GetBuffer函数。
char *p;
CString str="hello";
p=str.GetBuffer(str.GetLength());
str.ReleaseBuffer();

将CString转换成char * 时
CString str("aaaaaaa");
strcpy(str.GetBuffer(10),"aa");
str.ReleaseBuffer();
当我们需要字符数组时调用GetBuffer(int n),其中n为我们需要的字符数组的长度.使用完成后一定要马上调用ReleaseBuffer();
还有很重要的一点就是,在能使用const char *的地方,就不要使用char *

2、memcpy:
CString mCS=_T("cxl");
char mch[20];
memcpy(mch,mCS,20);

3、用LPCTSTR强制转换: 尽量不使用
char *ch;
CString str;
ch=(LPSTR)(LPCTSTR)str;

CString str = "good";
char *tmp;
sprintf(tmp,"%s",(LPTSTR)(LPCTSTR)str);

4、
CString Msg;
Msg=Msg+"abc";
LPTSTR lpsz;
lpsz = new TCHAR[Msg.GetLength()+1];
_tcscpy(lpsz, Msg);
char * psz;
strcpy(psz,lpsz);

CString类向const char *转换
char a[100];
CString str("aaaaaa");
strncpy(a,(LPCTSTR)str,sizeof(a));
或者如下:
strncpy(a,str,sizeof(a));
以上两种用法都是正确地. 因为strncpy的第二个参数类型为const char *.所以编译器会自动将CString类转换成const char *.

CString转LPCTSTR (const char *)
CString cStr;
const char *lpctStr=(LPCTSTR)cStr;

LPCTSTR转CString
LPCTSTR lpctStr;
CString cStr=lpctStr;

将char*类型的变量赋给CString型的变量
可以直接赋值,如:
CString myString = "This is a test";
也可以利用构造函数,如:
CString s1("Tom");

将CString类型的变量赋给char []类型(字符串)的变量
1、sprintf()函数
CString str = "good";
char tmp[200] ;
sprintf(tmp, "%s",(LPCSTR)str);
(LPCSTR)str这种强制转换相当于(LPTSTR)(LPCTSTR)str
CString类的变量需要转换为(char*)的时,使用(LPTSTR)(LPCTSTR)str

然而,LPCTSTR是const char *,也就是说,得到的字符串是不可写的!将其强制转换成LPTSTR去掉const,是极为危险的!
一不留神就会完蛋!要得到char *,应该用GetBuffer()或GetBufferSetLength(),用完后再调用ReleaseBuffer()。

2、strcpy()函数
CString str;
char c[256];
strcpy(c, str);

char mychar[1024];
CString source="Hello";
strcpy((char*)&mychar,(LPCTSTR)source);

关于CString的使用
1、指定 CString 形参
对于大多数需要字符串参数的函数,最好将函数原型中的形参指定为一个指向字符 (LPCTSTR) 而非 CString 的 const 指针。
当将形参指定为指向字符的 const 指针时,可将指针传递到 TCHAR 数组(如字符串 ["hi there"])或传递到 CString 对象。
CString 对象将自动转换成 LPCTSTR。任何能够使用 LPCTSTR 的地方也能够使用 CString 对象。

2、如果某个形参将不会被修改,则也将该参数指定为常数字符串引用(即 const CString&)。如果函数要修改该字符串,
则删除 const 修饰符。如果需要默认为空值,则将其初始化为空字符串 [""],如下所示:
void AddCustomer( const CString& name, const CString& address, const CString& comment = "" );

3、对于大多数函数结果,按值返回 CString 对象即可。

串的基本运算
对于串的基本运算,很多高级语言均提供了相应的运算符或标准的库函数来实现。
为叙述方便,先定义几个相关的变量:
char s1[20]="dir/bin/appl",s2[20]="file.asm",s3[30],*p;
int result;
下面以C语言中串运算介绍串的基本运算
1、求串长
int strlen(char *s);//求串s的长度
【例】printf("%d",strlen(s1)); //输出s1的串长12

2、串复制
char *strcpy(char *to,*from);//将from串复制到to串中,并返回to开始处指针
【例】strcpy(s3,s1); //s3="dir/bin/appl",s1串不变

3、联接
char *strcat(char *to,char *from);//将from串复制到to串的末尾,
//并返回to串开始处的指针
【例】strcat(s3,"/"); //s3="dir/bin/appl/"
strcat(s3,s2);//s3="dir/bin/appl/file.asm"

4、串比较
int strcmp(char *s1,char *s2);//比较s1和s2的大小,
//当s1s2和s1=s2时,分别返回小于0、大于0和等于0的值
【例】result=strcmp("baker","Baker"); //result>0
result=strcmp("12","12");//result=0
result=strcmp("Joe","joseph")//result<0

5、字符定位
char *strchr(char *s,char c);//找c在字符串s中第一次出现的位置,
//若找到,则返回该位置,否则返回NULL
【例】p=strchr(s2,'.'); //p指向"file"之后的位置
     if(p) strcpy(p,".cpp");//s2="file.cpp"

注意:
 ①上述操作是最基本的,其中后 4个操作还有变种形式:strncpy,strncath和strnchr。
 ②其它的串操作见C的。在不同的高级语言中,对串运算的种类及符号都不尽相同
 ③其余的串操作一般可由这些基本操作组合而成

【例】求子串的操作可如下实现:
void substr(char *sub,char *s,int pos,int len){
//s和sub是字符数组,用sub返回串s的第pos个字符起长度为len的子串
//其中0<=pos<=strlen(s)-1,且数组sub至少可容纳len+1个字符。
if (pos<0||pos>strlen(s)-1||len<0)
Error("parameter error!");
strncpy(sub,&s[pos],len); //从s[pos]起复制至多len个字符到sub

字符串、字符数组、数组、指针关系总结。

2008-10-25 发表在 程序开发 | 查看 56 次 | 评论
【规则】
1string为普通类型,不是数组型,只是其内容为字符串;
2’a’”a”的区别:’a’为字符,”a”为字符串;单引号只能定义一个字符,双引号可以定义多个字符,即字符串。
3、指针可以通过加减、自加减改变其值,数组不可以。
4、以数组定义的字符串可以修改其中的字符,以指针定义的字符串不能修改其中的字符。
 
例如:
char a[]=”hello”;
cout<<a;               //输出 hello
cout<<*(a+1);      //输出 e,即数组a的第2个字符,即a[1]
cout<<a[1];          //输出 e,即数组a的第2个字符,与上同
cout<<*a++;        //错误,规则3,数组名是常量,不可以改变其值
cout<<*a;            //输出 h,即数组a的第一个字符
cout<<a;              //输出hello
*a=”L”;            //错误,规则2,*a为字符,”L”为字符串,不匹配
*a=’L’;            //正确,规则4,将数组第一个字符修改为L
cout<<a;              //输出 Lello
a[2]=’6’;          //正确,规则4,将数组第3个字符修改为’6’
cout<<a;              //输出 Le6lo
 
char *a="hello";
cout<<a;            //输出 hello
cout<<*(a+1);    //输出 e,即字符串a的第2个字符
cout<<a[1];        //输出 e,即字符串a的第2个字符,这里也可以视为数组a
cout<<*a++;      //输出 h,,规则3,即先计算*a,再使a=a+1
cout<<*a;          //输出 e,因上一行中a已经自加1
cout<<a;            //输出 ello,因为指针a已经发生变化,指向了下一个地址,即指向以e开头的字符串
*a=’L’;         //错误,规则4,不能修改指针定义的字符串的字符。
                           //【注意】此处编译可通过,运行时会出错,提示“不能写入”
a[2]=’6’;      //错误,规则4,不能修改指针定义的字符串的字符。
 

 

string型(需要string头文件)
char型(不需要string头文件)
 
 
char a=’m’;
正确
 
 
char a=”m”;
错误
 
 
char *a=”m”;
正确
 
 
char a[]=”m”;
正确
string a=”hello”;
正确
char a=”hello”;
错误
string *a=”hello”;
错误
char *a=”hello”;
正确
string a[]=”hello”;
错误
char a[]=”hello”;
正确
string *p=a;
错误
char *p=a;
正确
string *p=&a;
正确
char *p=&a;
错误
cout<<a;
a的值
cout<<a;
a的值
cout<<*a;
错误
cout<<*a;
a的首字符
cout<<&a;
a的地址
cout<<&a;
a的地址
 

1、 字符数组相当于字符串

2、其末尾字符为’ \0 ‘

3、其长度=有效字符数+1,在定义字符数组长度时切记。

4、初始化方法
① char arr[6] = {"hello"};
② char arr[6] = "hello"; //与第①种相同,一般使用这一个
③ char arr[6] = {‘h’,'e’,'l’,'l’,'o’,'\0′}; //主要在特殊字符时使用

5、"a" 和 ‘a’ 的区别:前者是字符串,占用2个字节空间,后者是字符,占用1个字节空间。

6、数组长度计算:
(1)sizeof
方法:sizeof(数组名)/ sizeof(数组类型名)
说明:数组占用字节除以数组类型所占字节,结果为数组元素个数
(2)strlen
说明:strlen,求字符串有效长度
方法:strlen(字符数组名) //结果为字符数组有效字符长度,不包括末尾的’ \0′

对于char arr[]="hello";来说,sizeof(arr)为6,包含了数组末尾的’\0′,而strlen(arr)为5,不包含末尾的’\0′;
而对于其它类型的数组来说,没有strlen命令,sizeof(数组名)计算的结果就是这个数组所占用的字节数。

7、字符数组输出的特殊性

C++规定,数组的名称就是数组的首地址,对于a[ ]来说,a就是数组a的地址,但是在输出字符数组时,却有特殊的地方:

 

#include<iostream>
using namespace std;
void main()
{
  char a[]="How a you!";
  cout<<"a="<<a<<endl;
  char c[]={‘H’,'o’,'w’,’ ‘,’a',’ ‘,’y',’o',’u',’!',’\0′};
  cout<<"c="<<c<<endl;

  int b[]={1,2,3,4,5,6,7,8,9};
  cout<<"b="<<b<<endl;
}

对常规数组来说,输出要使用循环。但是在上例中,输出字符数组(字符串)时,使用cout可以直接输出。

运行结果:

a=How a you!
c=How a you!
b=0012FF44

由此可见:

不管是用双引号还是单引号初始化的字符数组,用cout<<字符数组名 的格式,都可以直接输出该数组。而不是输出数组的首地址;
要输出字符数组的首地址,必须使用cout<<&数组名 的格式。

而输出整型数组及其它数组时,使用cout<<数组名 ,输出的只能是数组的首地址。
要输出整个数组,必须使用循环。