2005年05月16日

Windows系列操作系统对线程间通信的支持           ——管道与邮路

Windows应用程序间数据通讯的基本方式有四种。最简单的是利用剪切板;另一种是DDEDynamic Data Exchange动态数据交换),它利用一种公共的协议实现两个或多个应用程序之间的通讯;再者是通过内存映射文件,内存映射可以将一个进程的一段虚拟地址映射为一个文件,然后其它的进程可以共享该段虚拟地址;最后就是通过管道与邮路实现进程间数据通信。

要讨论管道与邮路之前先让我们来复习一下这几个概念:进程、线程。微软官方对进程和线程的定义如下

进程:用最简洁的话来说,进程就是一个正在执行的程序,一个或多个线程在进程中运行,线程是操作系统分配CPU运算时间的最小单位。每一个进程都提供了运行一个程序所必需的资源,一个进程具有4GB的虚拟地址空间(Windows NT Server Enterprise EditionWindows 2000 Advanced Server中低3GB虚拟地址空间供进程使用,高1GB供操作系统的内核代码使用。Windows NT/2000中低2GB供进程使用,高2GB供操作系统内核代码使用。Windows9X0——64K只读空间用来装入Microsoft DOS信息,64K——4M装入DOS的兼容代码,4M——2GB的私有空间供进程使用,2GB——3GB的共享空间装入各种DLL代码,3GB——4GB为共享的系统内核代码空间,其中 共享的2GB——4GB的空间是99%的“内存无效页错误”、“General Protect Error(GPE)”及蓝屏的罪魁祸首。),可执行代码,数据,对象句柄,环境变量,优先权以及设置最大化最小化的功能。每一个进程都从一个主线程开始执行,但可以在它所拥有的线程中创建额外的线程。一个进程的所有线程共享进程的虚拟地址空间和系统资源,一个线程的资源包括线程的机器寄存器设置,内核堆栈,线程环境变量和进程虚拟地址中的用户堆栈。

让我们再来看看微软官方对管道和邮路是怎么解释的。

管道(pipe)是进程用来通讯的共享内存区域。一个进程往管道中写入信息,而其它的进程可以从管道中读出信息。如其名,管道是进程间数据交流的通道。邮路(Mailslots)的功能与管道类似,也是进程间通讯(interprocess communicationsIPC)的媒介,只不过其具体实现方式与管道有些差别。一个基于Win32的应用程序可以在邮路中储存消息,这些消息通常通过网络发往一个指定的计算机或某域名(域是共享一个组名的一组工作站或服务器。)下的所有计算机。你也可以使用命名管道代替邮路来进行进程间通信。命名管道最适合用来两个进程间的消息传递,邮路则更适合一个进程向多个进程广播消息。邮路具有一个重要的特点,它使用数据包广播消息。广播(broadcast)是网络传输中使用的术语,它意味着接收方收到数据后不发送确认消息通知发送方。而管道(这里的管道指命名管道,有关命名管道以下详解。)则不同,它更类似于打电话,你只对一个当事人说话,但是你却非常清楚你的话都被对方听到。邮路和管道一样,也是一个虚拟文件,它保存在内存中,但是你却必须使用普通的Win32文件函数访问它,比如CreateFileReadFileWriteFile等。邮路中储存的数据可以是任何形式的,唯一的要求是不得超过64K。与磁盘文件不同的是,邮路是一个临时的对象,当某个邮路所有的句柄都关闭的时候,该邮路及其中的数据就被删除。

管道的类型有两种:匿名管道和命名管道。匿名管道是不命名的,它最初用于在本地系统中父进程与它启动的子进程之间的通信。命名管道更高级,它由一个名字来标识,以使客户端和服务端应用程序可以通过它进行彼此通信。而且,Win32命名管道甚至可以在不同系统的进程间使用,这使它成为许多客户/服务器应用程序的理想之选。

就像水管连接两个地方并输送水一样,软件的管道连接两个进程并输送数据。一个一个管道一旦被建立,它就可以象文件一样被访问,并且可以使用许多与文件操作同样的函数。可以使用CreateFile函数获取一个已打开的管道的句柄,或者由另一个进程提供一个句柄。使用WriteFile函数向管道写入数据,之后这些数据可以被另外的进程用ReadFile函数读取。管道是系统对象,因此管道的句柄在不需要时必须使用CloseHandle函数关闭。

匿名管道只能单向传送数据,而命名管道可以双向传送。管道可以以比特流形式传送任意数量的数据。命名管道还可以将数据集合到称为消息的数据块中。命名管道甚至具有通过网络连接多进程的能力。但遗憾的是Windows9X不支持创建命名管道,它只能在WindowsNT系列(如Windows NTWindows 2000Windows XP)的操作系统上创建。

当讨论管道时,通常涉及到两个进程:客户进程和服务进程。服务进程负责创建管道。客户进程连接到管道。服务进程可以创建一个管道的多个实例,以此支持多个客户进程。

匿名管道用以下函数创建:

BOOL CreatePipe(


      PHANDLE hReadPipe,                       // 用于读操作的句柄
      PHANDLE hWritePipe,                      // 用于写操作的句柄
      LPSECURITY_ATTRIBUTES lpPipeAttributes,  // 描述安全信息的一个结构
      DWORD nSize                              // 管道大小

);

命名管道用以下函数创建:

HANDLE CreateNamedPipe(


  LPCTSTR lpName,                             // 管道名
      DWORD dwOpenMode,                           // 管道打开方式
      DWORD dwPipeMode,                           // 管道模式
  DWORD nMaxInstances,                        // 该管道最大的实例数量
  DWORD nOutBufferSize,                       // 输出缓冲区大小
  DWORD nInBufferSize,                        // 输入缓冲区大小
  DWORD nDefaultTimeOut,                      // 指定默认的超时时间
  LPSECURITY_ATTRIBUTES lpSecurityAttributes  // 描述安全信息的一个结构

);

管道由以下函数删除:

BOOL CloseHandle(


  HANDLE hObject   // 管道句柄

);

其它的管道函数简介如下:

CallNamedPipe:连接到一个命名管道,读取或写入数据之后关闭它。

ConnectNamedPipe:服务进程准备好一个连接到客户进程的管道,并等待一个客户进程连接上为止。

DisconnectNamedPipe:服务端用来断开与客户端的连接。

GetNamedPipeHandleState:获取一个命名管道的状态信息。

GetNamedPipeInfo:获取一个命名管道的信息。

PeekNamedPipe:从一个匿名或命名管道中拷贝数据到一个缓冲区。

SetNamedPipeHandleState:设置管道的类型及其它状态信息,比如说是比特流还是消息流管道。

TransactNamedPipe:从一个消息管道读消息或向其写入消息。

WaitNamedPipe:客户进程用来连接到一个命名管道。

邮路是由邮路服务进程创建。当邮路服务进程创建了一个邮路后,便返回该邮路句柄。当某个进程需要从该邮路中读取消息时,它必须提供该句柄。只有创建该邮路的进程,或以某种机制(比如继承)获得该邮路句柄的进程能够从邮路中读取消息。与管道不同,所有的邮路都是从属于创建它的本地进程的,你无法创建一个远程的邮路。邮路的客户进程具有向邮路写入消息的权限。任何进程只要获得了邮路的名字,就可以往里面写入消息,新的消息将放在邮路的消息队列后面。

邮路能在一个域中广播消息。如果域中几个进程每个都用相同的名字创建了一个邮路,则它们都会收到送往该邮路的消息。

在邮路中传递的消息如果小于425字节,那么它们会以数据包的形式传递。大于425字节的消息会以其它的方式传输,在这种情况下,你只能从一个客户进程传递给一个服务进程,而不能从一个客户进程传递给多个服务进程。且Windows NT不支持大于等于425个字节的消息传递。

邮路由以下函数创建:

HANDLE CreateMailslot(


  LPCTSTR lpName,                            // 邮路名
  DWORD nMaxMessageSize,                     // 消息最大的大小
  DWORD lReadTimeout,                        // 读取操作的超时时间
  LPSECURITY_ATTRIBUTES lpSecurityAttributes // 描述安全信息的一个结构

);

邮路由以下函数删除:

BOOL CloseHandle(


  HANDLE hObject   // 邮路句柄

);

其它邮路函数简介如下:

GetMailslotInfo:获取指定邮路的相关信息。

SetMailslotInfo:设置指定邮路的相关信息。

Windows系列操作系统对管道和邮路的支持就是这样,除此之外,内存映射文件也是进程间数据交流的重要方式,但限于篇幅,本文不再具体介绍。

2005年05月05日

C++中的模板(template)

  作者:张笑猛   来源:http://objects.nease.net/

网上我最喜欢的技术文章是类似某何君所著“CVS快速入门”或者“UML reference card”之类,简短扼要,可以非常快的领着你进入一个新天地。而对于比较长的文章我通常是将其保存到硬盘上,然后准备着“以后有时间”的时候再看,但它们通常的命运都是“闲坐说玄宗”,直到某一天在整理硬盘时将它们以“不知所云”入罪,一并删除。
 这篇小文主要是针对刚刚接触模板概念的读者,希望能帮助读者学习模板的使用。为了避免本文也在诸公的硬盘上遭逢厄运,我决定写的短些。“以后有时间”的时候再补充些内容。
TOC
1. 简介
2. 语法
3. 使用技巧
  3.1 语法检查
  3.2 继承
  3.3 静态成员
3.4 模板类的运用
4. 参考资料
 
1. 简介
模板是C++在90年代引进的一个新概念,原本是为了对容器类(container classes)的支持[1],但是现在模板产生的效果已经远非当初所能想象。
简单的讲,模板就是一种参数化(parameterized)的类或函数,也就是类的形态(成员、方法、布局等)或者函数的形态(参数、返回值等)可以被参数改变。更加神奇的是这里所说的参数,不光是我们传统函数中所说的数值形式的参数,还可以是一种类型(实际上稍微有一些了解的人,更多的会注意到使用类型作为参数,而往往忽略使用数值作为参数的情况)。
举个常用的例子来解释也许模板就从你脑袋里的一个模糊的概念变成活生生的代码了:
在C语言中,如果我们要比较两个数的大小,常常会定义两个宏:
#define min(a,b) ((a)>(b)?(b):(a))
#define max(a,b) ((a)>(b)?(a):(b))
这样你就可以在代码中:
return min(10, 4);
或者:
return min(5.3, 18.6);
这两个宏非常好用,但是在C++中,它们并不像在C中那样受欢迎。宏因为没有类型检查以及天生的不安全(例如如果代码写为min(a++, b–);则显然结果非你所愿),在C++中被inline函数替代。但是随着你将min/max改为函数,你立刻就会发现这个函数的局限性 —— 它不能处理你指定的类型以外的其它类型。例如你的min()声明为:
int min(int a, int b);
则它显然不能处理float类型的参数,但是原来的宏却可以很好的工作!你随后大概会想到函数重载,通过重载不同类型的min()函数,你仍然可以使大部分代码正常工作。实际上,C++对于这类可以抽象的算法,提供了更好的办法,就是模板:
template <class T> const T & min(const T & t1, const T & t2) {
    return t1>t2?t2:t1;
}
这是一个模板函数的例子。在有了模板之后,你就又自由了,可以像原来在C语言中使用你的min宏一样来使用这个模板,例如:
return min(10,4);
也可以:
return min(5.3, 18.6)
你发现了么?你获得了一个类型安全的、而又可以支持任意类型的min函数,它是否比min宏好呢?
当然上面这个例子只涉及了模板的一个方面,模板的作用远不只是用来替代宏。实际上,模板是泛化编程(Generic Programming)的基础。所谓的泛化编程,就是对抽象的算法的编程,泛化是指可以广泛的适用于不同的数据类型。例如我们上面提到的min算法。
2. 语法
你千万不要以为我真的要讲模板的语法,那太难为我了,我只是要说一下如何声明一个模板,如何定义一个模板以及常见的语法方面的问题。
template<> 是模板的标志,在<>中,是模板的参数部分。参数可以是类型,也可以是数值。例如:
template<class T, T t>
class Temp{
public:
    …
    void print() { cout << t << endl; }
private:
    T t_;
};
在这个声明中,第一个参数是一个类型,第二个参数是一个数值。这里的数值,必须是一个常量。例如针对上面的声明:
Temp<int, 10> temp; // 合法
int i = 10;
Temp<int, i> temp; // 不合法
const int j = 10;
Temp<int, j> temp; // 合法
参数也可以有默认值:
template<class T, class C=char> …
默认值的规则与函数的默认值一样,如果一个参数有默认值,则其后的每个参数都必须有默认值。
参数的名字在整个模板的作用域内有效,类型参数可以作为作用域内变量的类型(例如上例中的T t_),数值型参数可以参与计算,就象使用一个普通常数一样(例如上例中的cout << t << endl)。
模板有个值得注意的地方,就是它的声明方式。以前我一直认为模板的方法全部都是隐含为inline的,即使你没有将其声明为inline并将函数体放到了类声明以外。这是模板的声明方式给我的错觉,实际上并非如此。我们先来看看它的声明,一个作为接口出现在头文件中的模板类,其所有方法也都必须与类声明出现在一起。用通俗的话来说,就是模板类的函数体也必须出现在头文件中(当然如果这个模板只被一个C++程序文件使用,它当然也可以放在.cc中,但同样要求类声明与函数体必须出现在一起)。这种要求与inline的要求一样,因此我一度认为它们隐含都是inline的。但是在Thinking In C++[2]中,明确的提到了模板的non-inline function,就让我不得不改变自己的想法了。看来正确的理解应该是:与普通类一样,声明为inline的,或者虽然没有声明为inline但是函数体在类声明中的才是inline函数。
澄清了inline的问题候,我们再回头来看那些我们写的包含了模板类的丑陋的头文件,由于上面提到的语法要求,头文件中除了类接口之外,到处充斥着实现代码,对用户来说,十分的不可读。为了能像传统头文件一样,让用户尽量只看到接口,而不用看到实现方法,一般会将所有的方法实现部分,放在一个后缀为.i或者.inl的文件中,然后在模板类的头文件中包含这个.i或者.inl文件。例如:
// start of temp.h
template<class T> class Temp{
public:
    void print();
};
 #include "temp.inl"
// end of temp.h
// start of temp.inl
template<class T> void Temp<T>::print() {
    …
}
// end of temp.inl
通过这样的变通,即满足了语法的要求,也让头文件更加易读。模板函数也是一样。
普通的类中,也可以有模板方法,例如:
class A{
public:
    template<class T> void print(const T& t) { …}
    void dummy();
};
对于模板方法的要求与模板类的方法一样,也需要与类声明出现在一起。而这个类的其它方法,例如dummy(),则没有这样的要求。
3. 使用技巧
知道了上面所说的简单语法后,基本上就可以写出自己的模板了。但是在使用的时候还是有些技巧。
3.1 语法检查
对模板的语法检查有一部分被延迟到使用时刻(类被定义[3],或者函数被调用),而不是像普通的类或者函数在被编译器读到的时候就会进行语法检查。因此,如果一个模板没有被使用,则即使它包含了语法的错误,也会被编译器忽略,这是语法检查问题的第一个方面,这不常遇到,因为你写了一个模板就是为了使用它的,一般不会放在那里不用。与语法检查相关的另一个问题是你可以在模板中做一些假设。例如:
template<class T> class Temp{
public:
    Temp(const T & t): t_(t) {}
    void print() { t.print();}
private:
    T t_;
};
在这个模板中,我假设了T这个类型是一个类,并且有一个print()方法(t.print())。我们在简介中的min模板中其实也作了同样的假设,即假设T重载了’>’操作符。
因为语法检查被延迟,编译器看到这个模板的时候,并不去关心T这个类型是否有print()方法,这些假设在模板被使用的时候才被编译器检查。只要定义中给出的类型满足假设,就可以通过编译。
之所以说“有一部分”语法检查被延迟,是因为有些基本的语法还是被编译器立即检查的。只有那些与模板参数相关的检查才会被推迟。如果你没有写class结束后的分号,编译器不会放过你的。
3.2 继承
模板类可以与普通的类一样有基类,也同样可以有派生类。它的基类和派生类既可以是模板类,也可以不是模板类。所有与继承相关的特点模板类也都具备。但仍然有一些值得注意的地方。
假设有如下类关系:
template<class T> class A{ … };
 |
+– A<int> aint;
 |
+– A<double> adouble;
则aint和adouble并非A的派生类,甚至可以说根本不存在A这个类,只有A<int>和A<doubl>这两个类。这两个类没有共同的基类,因此不能通过类A来实现多态。如果希望对这两个类实现多态,正确的类层次应该是:
class Abase {…};
template<class T> class A: public Abase {…};
 |
+– A<int> aint;
 |
+– A<double> adouble;
也就是说,在模板类之上增加一个抽象的基类,注意,这个抽象基类是一个普通类,而非模板。
再来看下面的类关系:
template<int i> class A{…};
 |
+– A<10> a10;
 |
+– A<5> a5;
在这个情况下,模板参数是一个数值,而不是一个类型。尽管如此,a10和a5仍然没有共同基类。这与用类型作模板参数是一样的。
3.3 静态成员
与上面例子类似:
template<class T> class A{ static char a_; };
 |
+– A<int> aint1, aint2;
 |
+– A<double> adouble1, adouble2;
这里模板A中增加了一个静态成员,那么要注意的是,对于aint1和adouble1,它们并没有一个共同的静态成员。而aint1与aint2有一个共同的静态成员(对adouble1和adouble2也一样)。
这个问题实际上与继承里面讲到的问题是一回事,关键要认识到aint与adouble分别是两个不同类的实例,而不是一个类的两个实例。认识到这一点后,很多类似问题都可以想通了。
3.4 模板类的运用
模板与类继承都可以让代码重用,都是对具体问题的抽象过程。但是它们抽象的侧重点不同,模板侧重于对于算法的抽象,也就是说如果你在解决一个问题的时候,需要固定的step1 step2…,那么大概就可以抽象为模板。而如果一个问题域中有很多相同的操作,但是这些操作并不能组成一个固定的序列,大概就可以用类继承来解决问题。以我的水平还不足以在这么高的层次来清楚的解释它们的不同,这段话仅供参考吧。
模板类的运用方式,更多情况是直接使用,而不是作为基类。例如人们在使用STL提供的模板时,通常直接使用,而不需要从模板库中提供的模板再派生自己的类。这不是绝对的,我觉得这也是模板与类继承之间的以点儿区别,模板虽然也是抽象的东西,但是它往往不需要通过派生来具体化。
在设计模式[4]中,提到了一个模板方法模式,这个模式的核心就是对算法的抽象,也就是对固定操作序列的抽象。虽然不一定要用C++的模板来实现,但是它反映的思想是与C++模板一致的。
4. 参考资料
[1] 深度C++对象模型,Stanley B.Lippman, 侯捷译
[2] Thinking In C++ 2nd Edition Volumn 1, Bruce Eckel
[3] 定义– 英文为definition,意思是"Make this variable here",参见[2] p93
[4] Design Patterns – Elements of Reusable Object-Oriented Software GOF
2005年05月04日
C++之模板(Template)
原创:Greenfood 2002年4月22日

模板是C++提供进行编程的一种类书工具。所以在看本文以前请先了解一下C++类书的概念及相关的知识。
1.模板的概念
模办是实现类属机制的一种工具,它的功能非常强,它是无约束类属机制和约束类属机制的集合。 它可以让用户构造模板函数。 
模板,对象, 函数之间的关系见下图:

  

2.函数模板与模板函数:

先见下例:

#include <iostream.h>

template<class T>            //模板声明

T max(T x,T y)               //定义模板
{ 
    return
(x>y)? x:y;
}

main()
{
      int i=10,j=56;
      float x1=50.34, x2=56.34;
      double y1=673.36, y2=465.972;
      cout<<"the max of i, j is:"<<max(i, j)<<"\n";
      cout<<"the max of x1, x2 is:" <<max(x1,x2)<<"\n";
      cout<<"the max of y1, y2 is:" <<max(y1,y2)<<"\n";
      return 1;
}

    上面的这个程序虽然只是实现一个很简单的比较大小的问题, 但如果不用模板, 而用我们以前的方法, 由于参数类型和返回值类型不同将需要三个函数来实现, 这样是十分麻烦的。模板成功的解决了这个问题, 程序中生成了三个模板函数, 其中max(i,j)用模板实参int将类型实参数T进行了实例化;max(x1,x1)用模板实参float将类型参数T进行了实例化;max(y1,y2)用模板实参double将类型参数T进行了实例化。

    从上面的例子我们可以看出, 函数模板提供了一类函数的抽象, 它以任意类型T为参数及函数返回值。由一个函数模板产生的函数称为模板函数, 它是函数模板的具体实例。

函数模板和模板函数的关系图:

   要提醒大家注意的一点是:虽然模板参数T可以实例化成各种类型, 但是采用模板参数T的各种参数T的各参数之间必须保持完全一致的类型。例如:

T max(T x,T y)
{
return (x>y)?x:y;
}

void func(int i, char c, float)
{
   max(i,i);   //正确
   max(c,c);   //正确
   max(c,i);   //错误
   max(f,i);   //错误
}

    解决上面的问题就需要引入一个型的概念—重载, 既可以用非模板函数重载一个同名的函数模板。 重载有两种表述方式:

    1.利用函数模板的函数体

        使用次方法是必须注意各模板参数的实参类型必须一致。例如:

        int max(int,int)

    2.重新定义函数体

        对于要重新定义函数体的重载函数, 所带参数的类型可以随意, 就象一般的重载函数一样定义。

    定义重载函数特别要注意避免产生预期的和非预期的二义性。例如:

int max(int, int);

char max(int x,char y)
{
       //……
}


   当进行函数调用时有这样一个调用形式:
msx(i,j);
   此处iint,ffloat,这个函数调用就存在着二义性。这时的调用就需要按照一定的规则安排先后顺序, 这些规则就是函数模板与同名的非模板函数的重载方法均遵循的约定:

    (1)寻找一个参数完全匹配的函数, 如果找到了就调用它。

    (2)(1)失败后, 寻找一个函数模板, 使其实例化, 产生一个匹配的模板函数, 若找到了,就调用它。

    (3)(1)(2)均失败后, 再试一试低一级的对函数的重载方法,例如通过类型转换可产生参数匹配等, 若找到了,就调用它。

    (4)(1)(2)(3)均失败, 则这是一个错误的调用。

3.类模板与模板类

    类模板与模板类的概念

    一个类模板可以让用户为类定义一种模式, 使得类中的某些数据成员,某些成员函数的参数,某些成员函数的返回值,能取任意类型。

    定义一个类模板, 一般由两方面的内容:

    (1)首先要定义类, 其格式为:

template<class T>  //声明一个模板

class name
{
    //….
}

    name为类名,在类定义体中如采用数据类型的成员, 函数参数的前面需加上T。例如:

template<class T>

class vector
{
     T * data;
     int size;
     pulic:
     vetor(int);
     T&operator[](int);
     //…
};

    (2)在类定义体外定义成员函数时, 若此成员函数中有模板参数存在, 则需在函数体外进行模板声明, 并且在函数名前的类名后缀上“<T>”.例如:

template<class T>

vector<T>::vector(int i)
{
     //….
}

template<class T>

T&vector<T>::operator[](int i)
{
     //….
}

    类模板的使用

    类模板的使用实际上是将类模板实例化成一个具体的类, 它的格式为:

    类名<实际的类型>
    例如:

main()
{
     vector<int>x(5);
     for(int i=0; i<=5;i++)
     x[i]=i;
     for(i=0;i<5;++i)
     cout<<x[i]<<"";
     cout<<"\n";
}

    类模板和模板类之间的关系, 如图:

   

    类模板使用需要注意的几点:

    (1)在每个模板定义之前, 不管是类模板还是函数模板, 都需要在前面加上模板声明:

template<class T>

    (2)类模板和结构模板在使用时, 必须在名字后面缀上模板参数<T>,如:list<T>,node<T>.

2005年04月30日
 
1、DateTime 数字型
System.DateTime currentTime=new System.DateTime();
1.1 取当前年月日时分秒
currentTime=System.DateTime.Now;
1.2 取当前年
int 年=currentTime.Year;
1.3 取当前月
int 月=currentTime.Month;
1.4 取当前日
int 日=currentTime.Day;
1.5 取当前时
int 时=currentTime.Hour;
1.6 取当前分
int 分=currentTime.Minute;
1.7 取当前秒
int 秒=currentTime.Second;
1.8 取当前毫秒
int 毫秒=currentTime.Millisecond;
(变量可用中文)

2、Int32.Parse(变量) Int32.Parse("常量")
字符型转换 转为32位数字型

3、 变量.ToString()
字符型转换 转为字符串
12345.ToString("n"); //生成 12,345.00
12345.ToString("C"); //生成 ¥12,345.00
12345.ToString("e"); //生成 1.234500e+004
12345.ToString("f4"); //生成 12345.0000
12345.ToString("x"); //生成 3039 (16进制)
12345.ToString("p"); //生成 1,234,500.00%


4、变量.Length 数字型
取字串长度:
如: string str="中国";
int Len = str.Length ; //Len是自定义变量, str是求测的字串的变量名

5、System.Text.Encoding.Default.GetBytes(变量)
字码转换 转为比特码
如:byte[] bytStr = System.Text.Encoding.Default.GetBytes(str);
然后可得到比特长度:
len = bytStr.Length;

6、System.Text.StringBuilder("")
字符串相加,(+号是不是也一样?)
如:System.Text.StringBuilder sb = new System.Text.StringBuilder("");
sb.Append("中华");
sb.Append("人民");
sb.Append("共和国");

7、变量.Substring(参数1,参数2);
截取字串的一部分,参数1为左起始位数,参数2为截取几位。
如:string s1 = str.Substring(0,2);

8、String user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
取远程用户IP地址

9、穿过代理服务器取远程用户真实IP地址:
if(Request.ServerVariables["HTTP_VIA"]!=null){
string user_IP=Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
}else{
string user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
}

10、 Session["变量"];
存取Session值;
如,赋值: Session["username"]="小布什";

取值: Object objName=Session["username"];
String strName=objName.ToString();
清空: Session.RemoveAll();

11、String str=Request.QueryString["变量"];
用超链接传送变量。
如在任一页中建超链接:<a href=Edit.aspx?fbid=23>点击</a>
在Edit.aspx页中取值:String str=Request.QueryString["fdid"];

12、DOC对象.CreateElement("新建节点名");
创建XML文档新节点

13、父节点.AppendChild(子节点);
将新建的子节点加到XML文档父节点下

14、 父节点.RemoveChild(节点);
删除节点

15、Response
Response.Write("字串");
Response.Write(变量);
向页面输出。

Response.Redirect("URL地址");
跳转到URL指定的页面

16、char.IsWhiteSpce(字串变量,位数)——逻辑型
查指定位置是否空字符;
如:
string str="中国 人民";
Response.Write(char.IsWhiteSpace(str,2)); //结果为:True, 第一个字符是0位,2是第三个字符。

17、char.IsPunctuation(‘字符’) –逻辑型
查字符是否是标点符号
如:Response.Write(char.IsPunctuation(‘A’)); //返回:False

18、(int)’字符’
把字符转为数字,查代码点,注意是单引号。
如:
Response.Write((int)’中’); //结果为中字的代码:20013

19、(char)代码
把数字转为字符,查代码代表的字符。
如:
Response.Write((char)22269); //返回“国”字。

20、 Trim()
清除字串前后空格

21 、字串变量.Replace("子字串","替换为")
字串替换
如:
string str="中国";
str=str.Replace("国","央"); //将国字换为央字
Response.Write(str); //输出结果为“中央”

再如:(这个非常实用)

string str="这是<script>脚本";
str=str.Replace("<","<font><</font>"); //将左尖括号替换为<font> 与 < 与 </font> (或换为<,但估计经XML存诸后,再提出仍会还原)
Response.Write(str); //显示为:“这是<script>脚本”

如果不替换,<script>将不显示,如果是一段脚本,将运行;而替换后,脚本将不运行。
这段代码的价值在于:你可以让一个文本中的所有HTML标签失效,全部显示出来,保护你的具有交互性的站点。
具体实现:将你的表单提交按钮脚本加上下面代码:
string strSubmit=label1.Text; //label1是你让用户提交数据的控件ID。
strSubmit=strSubmit.Replace("<","<font><</font>");
然后保存或输出strSubmit。
用此方法还可以简单实现UBB代码。

22、Math.Max(i,j)
取i与j中的最大值
如 int x=Math.Max(5,10); // x将取值 10

加一点吧 23、字串对比……
加一点吧

23、字串对比一般都用: if(str1==str2){ } , 但还有别的方法:

(1)、
string str1; str2
//语法: str1.EndsWith(str2); __检测字串str1是否以字串str2结尾,返回布尔值.如:
if(str1.EndsWith(str2)){ Response.Write("字串str1是以"+str2+"结束的"); }

(2)、
//语法:str1.Equals(str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

(3)、
//语法 Equals(str1,str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

24、IndexOf() 、LastIndexOf()
查找字串中指定字符或字串首次(最后一次)出现的位置,返回索引值,如:
str1.IndexOf("字"); //查找“字”在str1中的索引值(位置)
str1.IndexOf("字串");//查找“字串”的第一个字符在str1中的索引值(位置)
str1.IndexOf("字串",3,2);//从str1第4个字符起,查找2个字符,查找“字串”的第一个字符在str1中的索引值(位置)

25、Insert()
在字串中指定索引位插入指定字符。如:
str1.Insert(1,"字");在str1的第二个字符处插入“字”,如果str1="中国",插入后为“中字国”;

26、PadLeft()、PadRight()
在字串左(或右)加空格或指定char字符,使字串达到指定长度,如:
<%
string str1="中国人";
str1=str1.PadLeft(10,’1′); //无第二参数为加空格
Response.Write(str1); //结果为“1111111中国人” , 字串长为10
%>

27、Remove()
从指定位置开始删除指定数的字符
字串对比一般都用: if(str1==str2){ } , 但还有别的方法:

1、
string str1; str2
//语法: str1.EndsWith(str2); __检测字串str1是否以字串str2结尾,返回布尔值.如:
if(str1.EndsWith(str2)){ Response.Write("字串str1是以"+str2+"结束的"); }

2、
//语法:str1.Equals(str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

3、
//语法 Equals(str1,str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

IndexOf()
查找字串中指定字符或字串首次出现的位置,返首索引值,如:
str1.IndexOf("字"); //查找“字”在str1中的索引值(位置)
str1.IndexOf("字串");//查找“字串”的第一个字符在str1中的索引值(位置)
str1.IndexOf("字串",3,2);//从str1第4个字符起,查找2个字符,查找“字串”的第一个字符在str1中的索引值(位置)

1.9 取中文日期显示——年月日时分
string strY=currentTime.ToString("f"); //不显示秒

1.10 取中文日期显示_年月
string strYM=currentTime.ToString("y");

1.11 取中文日期显示_月日
string strMD=currentTime.ToString("m");

1.12 取当前年月日,格式为:2003-9-23
string strYMD=currentTime.ToString("d");

1.13 取当前时分,格式为:14:24
string strT=currentTime.ToString("t");
更新一下, 上面不能编辑:

c#.net函数和方法集(大家一起来加啊)


1、DateTime 数字型
System.DateTime currentTime=new System.DateTime();
1.1 取当前年月日时分秒
currentTime=System.DateTime.Now;
1.2 取当前年
int 年=currentTime.Year;
1.3 取当前月
int 月=currentTime.Month;
1.4 取当前日
int 日=currentTime.Day;
1.5 取当前时
int 时=currentTime.Hour;
1.6 取当前分
int 分=currentTime.Minute;
1.7 取当前秒
int 秒=currentTime.Second;
1.8 取当前毫秒
int 毫秒=currentTime.Millisecond;
(变量可用中文)

1.9 取中文日期显示——年月日时分
string strY=currentTime.ToString("f"); //不显示秒

1.10 取中文日期显示_年月
string strYM=currentTime.ToString("y");

1.11 取中文日期显示_月日
string strMD=currentTime.ToString("m");

1.12 取中文年月日
string strYMD=currentTime.ToString("D");

1.13 取当前时分,格式为:14:24
string strT=currentTime.ToString("t");

1.14 取当前时间,格式为:2003-09-23T14:46:48
string strT=currentTime.ToString("s");

1.15 取当前时间,格式为:2003-09-23 14:48:30Z
string strT=currentTime.ToString("u");

1.16 取当前时间,格式为:2003-09-23 14:48
string strT=currentTime.ToString("g");

1.17 取当前时间,格式为:Tue, 23 Sep 2003 14:52:40 GMT
string strT=currentTime.ToString("r");

1.18获得当前时间 n 天后的日期时间
DateTime newDay = DateTime.Now.AddDays(100);

2、Int32.Parse(变量) Int32.Parse("常量")
字符型转换 转为32位数字型

3、 变量.ToString()
字符型转换 转为字符串
12345.ToString("n"); //生成 12,345.00
12345.ToString("C"); //生成 ¥12,345.00
12345.ToString("e"); //生成 1.234500e+004
12345.ToString("f4"); //生成 12345.0000
12345.ToString("x"); //生成 3039 (16进制)
12345.ToString("p"); //生成 1,234,500.00%


4、变量.Length 数字型
取字串长度:
如: string str="中国";
int Len = str.Length ; //Len是自定义变量, str是求测的字串的变量名

5、System.Text.Encoding.Default.GetBytes(变量)
字码转换 转为比特码
如:byte[] bytStr = System.Text.Encoding.Default.GetBytes(str);
然后可得到比特长度:
len = bytStr.Length;

6、System.Text.StringBuilder("")
字符串相加,(+号是不是也一样?)
如:System.Text.StringBuilder sb = new System.Text.StringBuilder("");
sb.Append("中华");
sb.Append("人民");
sb.Append("共和国");

7、变量.Substring(参数1,参数2);
截取字串的一部分,参数1为左起始位数,参数2为截取几位。
如:string s1 = str.Substring(0,2);

8、String user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
远程用户IP地址

9、穿过代理服务器取远程用户真实IP地址:
if(Request.ServerVariables["HTTP_VIA"]!=null){
string user_IP=Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
}else{
string user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
}

10、 Session["变量"];
存取Session值;
如,赋值: Session["username"]="小布什";

取值: Object objName=Session["username"];
String strName=objName.ToString();
清空: Session.RemoveAll();

11、String str=Request.QueryString["变量"];
用超链接传送变量。
如在任一页中建超链接:<a href=Edit.aspx?fbid=23>点击</a>
在Edit.aspx页中取值:String str=Request.QueryString["fdid"];

12、DOC对象.CreateElement("新建节点名");
创建XML文档新节点

13、父节点.AppendChild(子节点);
将新建的子节点加到XML文档父节点下

14、 父节点.RemoveChild(节点);
删除节点

15、Response
Response.Write("字串");
Response.Write(变量);
向页面输出。

Response.Redirect("URL地址");
跳转到URL指定的页面

16、char.IsWhiteSpce(字串变量,位数)——逻辑型
查指定位置是否空字符;
如:
string str="中国 人民";
Response.Write(char.IsWhiteSpace(str,2)); //结果为:True, 第一个字符是0位,2是第三个字符。

17、char.IsPunctuation(‘字符’) –逻辑型
查字符是否是标点符号
如:Response.Write(char.IsPunctuation(‘A’)); //返回:False

18、(int)’字符’
把字符转为数字,查代码点,注意是单引号。
如:
Response.Write((int)’中’); //结果为中字的代码:20013

19、(char)代码
把数字转为字符,查代码代表的字符。
如:
Response.Write((char)22269); //返回“国”字。

20、 Trim()
清除字串前后空格

21 、字串变量.Replace("子字串","替换为")
字串替换
如:
string str="中国";
str=str.Replace("国","央"); //将国字换为央字
Response.Write(str); //输出结果为“中央”

再如:(这个非常实用)

string str="这是<script>脚本";
str=str.Replace("<","<font><</font>"); //将左尖括号替换为<font> 与 < 与 </font> (或换为<,但估计经XML存诸后,再提出仍会还原)
Response.Write(str); //显示为:“这是<script>脚本”

如果不替换,<script>将不显示,如果是一段脚本,将运行;而替换后,脚本将不运行。
这段代码的价值在于:你可以让一个文本中的所有HTML标签失效,全部显示出来,保护你的具有交互性的站点。
具体实现:将你的表单提交按钮脚本加上下面代码:
string strSubmit=label1.Text; //label1是你让用户提交数据的控件ID。
strSubmit=strSubmit.Replace("<","<font><</font>");
然后保存或输出strSubmit。
用此方法还可以简单实现UBB代码。

22、Math.Max(i,j)
取i与j中的最大值
如 int x=Math.Max(5,10); // x将取值 10

23、字串对比一般都用: if(str1==str2){ } , 但还有别的方法:

(1)、
string str1; str2
//语法: str1.EndsWith(str2); __检测字串str1是否以字串str2结尾,返回布尔值.如:
if(str1.EndsWith(str2)){ Response.Write("字串str1是以"+str2+"结束的"); }

(2)、
//语法:str1.Equals(str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

(3)、
//语法 Equals(str1,str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

24、IndexOf() 、LastIndexOf()
查找字串中指定字符或字串首次(最后一次)出现的位置,返回索引值,如:
str1.IndexOf("字"); //查找“字”在str1中的索引值(位置)
str1.IndexOf("字串");//查找“字串”的第一个字符在str1中的索引值(位置)
str1.IndexOf("字串",3,2);//从str1第4个字符起,查找2个字符,查找“字串”的第一个字符在str1中的索引值(位置)

25、Insert()
在字串中指定索引位插入指定字符。如:
str1.Insert(1,"字");在str1的第二个字符处插入“字”,如果str1="中国",插入后为“中字国”;

26、PadLeft()、PadRight()
在字串左(或右)加空格或指定char字符,使字串达到指定长度,如:
<%
string str1="中国人";
str1=str1.PadLeft(10,’1′); //无第二参数为加空格
Response.Write(str1); //结果为“1111111中国人” , 字串长为10
%>

27、Remove()
从指定位置开始删除指定数的字符
<%
string str1="我是萨达姆的崇拜者之一";
Response.Write(str1.Remove(5,4)); //结果为“我是萨达姆之一”
%>

1、DateTime 数字型
System.DateTime currentTime=new System.DateTime();
1.1 取当前年月日时分秒
currentTime=System.DateTime.Now;
1.2 取当前年
int 年=currentTime.Year;
1.3 取当前月
int 月=currentTime.Month;
1.4 取当前日
int 日=currentTime.Day;
1.5 取当前时
int 时=currentTime.Hour;
1.6 取当前分
int 分=currentTime.Minute;
1.7 取当前秒
int 秒=currentTime.Second;
1.8 取当前毫秒
int 毫秒=currentTime.Millisecond;
(变量可用中文)

2、Int32.Parse(变量) Int32.Parse("常量")
字符型转换 转为32位数字型

3、 变量.ToString()
字符型转换 转为字符串
12345.ToString("n"); //生成 12,345.00
12345.ToString("C"); //生成 ¥12,345.00
12345.ToString("e"); //生成 1.234500e+004
12345.ToString("f4"); //生成 12345.0000
12345.ToString("x"); //生成 3039 (16进制)
12345.ToString("p"); //生成 1,234,500.00%


4、变量.Length 数字型
取字串长度:
如: string str="中国";
int Len = str.Length ; //Len是自定义变量, str是求测的字串的变量名

5、System.Text.Encoding.Default.GetBytes(变量)
字码转换 转为比特码
如:byte[] bytStr = System.Text.Encoding.Default.GetBytes(str);
然后可得到比特长度:
len = bytStr.Length;

6、System.Text.StringBuilder("")
字符串相加,(+号是不是也一样?)
如:System.Text.StringBuilder sb = new System.Text.StringBuilder("");
sb.Append("中华");
sb.Append("人民");
sb.Append("共和国");

7、变量.Substring(参数1,参数2);
截取字串的一部分,参数1为左起始位数,参数2为截取几位。
如:string s1 = str.Substring(0,2);

8、String user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
取远程用户IP地址

9、穿过代理服务器取远程用户真实IP地址:
if(Request.ServerVariables["HTTP_VIA"]!=null){
string user_IP=Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
}else{
string user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
}

10、 Session["变量"];
存取Session值;
如,赋值: Session["username"]="小布什";

取值: Object objName=Session["username"];
String strName=objName.ToString();
清空: Session.RemoveAll();

11、String str=Request.QueryString["变量"];
用超链接传送变量。
如在任一页中建超链接:<a href=Edit.aspx?fbid=23>点击</a>
在Edit.aspx页中取值:String str=Request.QueryString["fdid"];

12、DOC对象.CreateElement("新建节点名");
创建XML文档新节点

13、父节点.AppendChild(子节点);
将新建的子节点加到XML文档父节点下

14、 父节点.RemoveChild(节点);
删除节点

15、Response
Response.Write("字串");
Response.Write(变量);
向页面输出。

Response.Redirect("URL地址");
跳转到URL指定的页面

16、char.IsWhiteSpce(字串变量,位数)——逻辑型
查指定位置是否空字符;
如:
string str="中国 人民";
Response.Write(char.IsWhiteSpace(str,2)); //结果为:True, 第一个字符是0位,2是第三个字符。

17、char.IsPunctuation(‘字符’) –逻辑型
查字符是否是标点符号
如:Response.Write(char.IsPunctuation(‘A’)); //返回:False

18、(int)’字符’
把字符转为数字,查代码点,注意是单引号。
如:
Response.Write((int)’中’); //结果为中字的代码:20013

19、(char)代码
把数字转为字符,查代码代表的字符。
如:
Response.Write((char)22269); //返回“国”字。

20、 Trim()
清除字串前后空格

21 、字串变量.Replace("子字串","替换为")
字串替换
如:
string str="中国";
str=str.Replace("国","央"); //将国字换为央字
Response.Write(str); //输出结果为“中央”

再如:(这个非常实用)

string str="这是<script>脚本";
str=str.Replace("<","<font><</font>"); //将左尖括号替换为<font> 与 < 与 </font> (或换为<,但估计经XML存诸后,再提出仍会还原)
Response.Write(str); //显示为:“这是<script>脚本”

如果不替换,<script>将不显示,如果是一段脚本,将运行;而替换后,脚本将不运行。
这段代码的价值在于:你可以让一个文本中的所有HTML标签失效,全部显示出来,保护你的具有交互性的站点。
具体实现:将你的表单提交按钮脚本加上下面代码:
string strSubmit=label1.Text; //label1是你让用户提交数据的控件ID。
strSubmit=strSubmit.Replace("<","<font><</font>");
然后保存或输出strSubmit。
用此方法还可以简单实现UBB代码。

22、Math.Max(i,j)
取i与j中的最大值
如 int x=Math.Max(5,10); // x将取值 10

加一点吧 23、字串对比……
加一点吧

23、字串对比一般都用: if(str1==str2){ } , 但还有别的方法:

(1)、
string str1; str2
//语法: str1.EndsWith(str2); __检测字串str1是否以字串str2结尾,返回布尔值.如:
if(str1.EndsWith(str2)){ Response.Write("字串str1是以"+str2+"结束的"); }

(2)、
//语法:str1.Equals(str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

(3)、
//语法 Equals(str1,str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

24、IndexOf() 、LastIndexOf()
查找字串中指定字符或字串首次(最后一次)出现的位置,返回索引值,如:
str1.IndexOf("字"); //查找“字”在str1中的索引值(位置)
str1.IndexOf("字串");//查找“字串”的第一个字符在str1中的索引值(位置)
str1.IndexOf("字串",3,2);//从str1第4个字符起,查找2个字符,查找“字串”的第一个字符在str1中的索引值(位置)

25、Insert()
在字串中指定索引位插入指定字符。如:
str1.Insert(1,"字");在str1的第二个字符处插入“字”,如果str1="中国",插入后为“中字国”;

26、PadLeft()、PadRight()
在字串左(或右)加空格或指定char字符,使字串达到指定长度,如:
<%
string str1="中国人";
str1=str1.PadLeft(10,’1′); //无第二参数为加空格
Response.Write(str1); //结果为“1111111中国人” , 字串长为10
%>

27、Remove()
从指定位置开始删除指定数的字符
字串对比一般都用: if(str1==str2){ } , 但还有别的方法:

1、
string str1; str2
//语法: str1.EndsWith(str2); __检测字串str1是否以字串str2结尾,返回布尔值.如:
if(str1.EndsWith(str2)){ Response.Write("字串str1是以"+str2+"结束的"); }

2、
//语法:str1.Equals(str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

3、
//语法 Equals(str1,str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

IndexOf()
查找字串中指定字符或字串首次出现的位置,返首索引值,如:
str1.IndexOf("字"); //查找“字”在str1中的索引值(位置)
str1.IndexOf("字串");//查找“字串”的第一个字符在str1中的索引值(位置)
str1.IndexOf("字串",3,2);//从str1第4个字符起,查找2个字符,查找“字串”的第一个字符在str1中的索引值(位置)

1.9 取中文日期显示——年月日时分
string strY=currentTime.ToString("f"); //不显示秒

1.10 取中文日期显示_年月
string strYM=currentTime.ToString("y");

1.11 取中文日期显示_月日
string strMD=currentTime.ToString("m");

1.12 取当前年月日,格式为:2003-9-23
string strYMD=currentTime.ToString("d");

1.13 取当前时分,格式为:14:24
string strT=currentTime.ToString("t");
更新一下, 上面不能编辑:

c#.net函数和方法集(大家一起来加啊)


1、DateTime 数字型
System.DateTime currentTime=new System.DateTime();
1.1 取当前年月日时分秒
currentTime=System.DateTime.Now;
1.2 取当前年
int 年=currentTime.Year;
1.3 取当前月
int 月=currentTime.Month;
1.4 取当前日
int 日=currentTime.Day;
1.5 取当前时
int 时=currentTime.Hour;
1.6 取当前分
int 分=currentTime.Minute;
1.7 取当前秒
int 秒=currentTime.Second;
1.8 取当前毫秒
int 毫秒=currentTime.Millisecond;
(变量可用中文)

1.9 取中文日期显示——年月日时分
string strY=currentTime.ToString("f"); //不显示秒

1.10 取中文日期显示_年月
string strYM=currentTime.ToString("y");

1.11 取中文日期显示_月日
string strMD=currentTime.ToString("m");

1.12 取中文年月日
string strYMD=currentTime.ToString("D");

1.13 取当前时分,格式为:14:24
string strT=currentTime.ToString("t");

1.14 取当前时间,格式为:2003-09-23T14:46:48
string strT=currentTime.ToString("s");

1.15 取当前时间,格式为:2003-09-23 14:48:30Z
string strT=currentTime.ToString("u");

1.16 取当前时间,格式为:2003-09-23 14:48
string strT=currentTime.ToString("g");

1.17 取当前时间,格式为:Tue, 23 Sep 2003 14:52:40 GMT
string strT=currentTime.ToString("r");

1.18获得当前时间 n 天后的日期时间
DateTime newDay = DateTime.Now.AddDays(100);

2、Int32.Parse(变量) Int32.Parse("常量")
字符型转换 转为32位数字型

3、 变量.ToString()
字符型转换 转为字符串
12345.ToString("n"); //生成 12,345.00
12345.ToString("C"); //生成 ¥12,345.00
12345.ToString("e"); //生成 1.234500e+004
12345.ToString("f4"); //生成 12345.0000
12345.ToString("x"); //生成 3039 (16进制)
12345.ToString("p"); //生成 1,234,500.00%


4、变量.Length 数字型
取字串长度:
如: string str="中国";
int Len = str.Length ; //Len是自定义变量, str是求测的字串的变量名

5、System.Text.Encoding.Default.GetBytes(变量)
字码转换 转为比特码
如:byte[] bytStr = System.Text.Encoding.Default.GetBytes(str);
然后可得到比特长度:
len = bytStr.Length;

6、System.Text.StringBuilder("")
字符串相加,(+号是不是也一样?)
如:System.Text.StringBuilder sb = new System.Text.StringBuilder("");
sb.Append("中华");
sb.Append("人民");
sb.Append("共和国");

7、变量.Substring(参数1,参数2);
截取字串的一部分,参数1为左起始位数,参数2为截取几位。
如:string s1 = str.Substring(0,2);

8、String user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
远程用户IP地址

9、穿过代理服务器取远程用户真实IP地址:
if(Request.ServerVariables["HTTP_VIA"]!=null){
string user_IP=Request.ServerVariables["HTTP_X_FORWARDED_FOR"].ToString();
}else{
string user_IP=Request.ServerVariables["REMOTE_ADDR"].ToString();
}

10、 Session["变量"];
存取Session值;
如,赋值: Session["username"]="小布什";

取值: Object objName=Session["username"];
String strName=objName.ToString();
清空: Session.RemoveAll();

11、String str=Request.QueryString["变量"];
用超链接传送变量。
如在任一页中建超链接:<a href=Edit.aspx?fbid=23>点击</a>
在Edit.aspx页中取值:String str=Request.QueryString["fdid"];

12、DOC对象.CreateElement("新建节点名");
创建XML文档新节点

13、父节点.AppendChild(子节点);
将新建的子节点加到XML文档父节点下

14、 父节点.RemoveChild(节点);
删除节点

15、Response
Response.Write("字串");
Response.Write(变量);
向页面输出。

Response.Redirect("URL地址");
跳转到URL指定的页面

16、char.IsWhiteSpce(字串变量,位数)——逻辑型
查指定位置是否空字符;
如:
string str="中国 人民";
Response.Write(char.IsWhiteSpace(str,2)); //结果为:True, 第一个字符是0位,2是第三个字符。

17、char.IsPunctuation(‘字符’) –逻辑型
查字符是否是标点符号
如:Response.Write(char.IsPunctuation(‘A’)); //返回:False

18、(int)’字符’
把字符转为数字,查代码点,注意是单引号。
如:
Response.Write((int)’中’); //结果为中字的代码:20013

19、(char)代码
把数字转为字符,查代码代表的字符。
如:
Response.Write((char)22269); //返回“国”字。

20、 Trim()
清除字串前后空格

21 、字串变量.Replace("子字串","替换为")
字串替换
如:
string str="中国";
str=str.Replace("国","央"); //将国字换为央字
Response.Write(str); //输出结果为“中央”

再如:(这个非常实用)

string str="这是<script>脚本";
str=str.Replace("<","<font><</font>"); //将左尖括号替换为<font> 与 < 与 </font> (或换为<,但估计经XML存诸后,再提出仍会还原)
Response.Write(str); //显示为:“这是<script>脚本”

如果不替换,<script>将不显示,如果是一段脚本,将运行;而替换后,脚本将不运行。
这段代码的价值在于:你可以让一个文本中的所有HTML标签失效,全部显示出来,保护你的具有交互性的站点。
具体实现:将你的表单提交按钮脚本加上下面代码:
string strSubmit=label1.Text; //label1是你让用户提交数据的控件ID。
strSubmit=strSubmit.Replace("<","<font><</font>");
然后保存或输出strSubmit。
用此方法还可以简单实现UBB代码。

22、Math.Max(i,j)
取i与j中的最大值
如 int x=Math.Max(5,10); // x将取值 10

23、字串对比一般都用: if(str1==str2){ } , 但还有别的方法:

(1)、
string str1; str2
//语法: str1.EndsWith(str2); __检测字串str1是否以字串str2结尾,返回布尔值.如:
if(str1.EndsWith(str2)){ Response.Write("字串str1是以"+str2+"结束的"); }

(2)、
//语法:str1.Equals(str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

(3)、
//语法 Equals(str1,str2); __检测字串str1是否与字串str2相等,返回布尔值,用法同上.

24、IndexOf() 、LastIndexOf()
查找字串中指定字符或字串首次(最后一次)出现的位置,返回索引值,如:
str1.IndexOf("字"); //查找“字”在str1中的索引值(位置)
str1.IndexOf("字串");//查找“字串”的第一个字符在str1中的索引值(位置)
str1.IndexOf("字串",3,2);//从str1第4个字符起,查找2个字符,查找“字串”的第一个字符在str1中的索引值(位置)

25、Insert()
在字串中指定索引位插入指定字符。如:
str1.Insert(1,"字");在str1的第二个字符处插入“字”,如果str1="中国",插入后为“中字国”;

26、PadLeft()、PadRight()
在字串左(或右)加空格或指定char字符,使字串达到指定长度,如:
<%
string str1="中国人";
str1=str1.PadLeft(10,’1′); //无第二参数为加空格
Response.Write(str1); //结果为“1111111中国人” , 字串长为10
%>

27、Remove()
从指定位置开始删除指定数的字符
<%
string str1="我是萨达姆的崇拜者之一";
Response.Write(str1.Remove(5,4)); //结果为“我是萨达姆之一”
%>

2005年04月29日

C#中调用Windows API的要点

  在.Net Framework SDK文档中,关于调用Windows API的指示比较零散,并且其中稍全面一点的是针对Visual Basic .net讲述的。本文将C#中调用API的要点汇集如下,希望给未在C#中使用过API的朋友一点帮助。另外如果安装了Visual Studio .net的话,在C:\Program Files\Microsoft Visual Studio .NET\FrameworkSDK\Samples\Technologies\Interop\PlatformInvoke\WinAPIs\CS目录下有大量的调用API的例子。

 一、调用格式


  using System.Runtime.InteropServices; //引用此名称空间,简化后面的代码
  …
  //使用DllImportAttribute特性来引入api函数,注意声明的是空方法,即方法体为空。
  [DllImport("user32.dll")]
  public static extern ReturnType FunctionName(type arg1,type arg2,…);
  //调用时与调用其他方法并无区别

  可以使用字段进一步说明特性,用逗号隔开,如:



  [ DllImport( "kernel32", EntryPoint="GetVersionEx" )]


  DllImportAttribute特性的公共字段如下:
  1、CallingConvention 指示向非托管实现传递方法参数时所用的 CallingConvention 值。
  CallingConvention.Cdecl : 调用方清理堆栈。它使您能够调用具有 varargs 的函数。
  CallingConvention.StdCall : 被调用方清理堆栈。它是从托管代码调用非托管函数的默认约定。

  2、CharSet 控制调用函数的名称版本及指示如何向方法封送 String 参数。

  此字段被设置为 CharSet 值之一。如果 CharSet 字段设置为 Unicode,则所有字符串参数在传递到非托管实现之前都转换成 Unicode 字符。这还导致向 DLL EntryPoint 的名称中追加字母“W”。如果此字段设置为 Ansi,则字符串将转换成 ANSI 字符串,同时向 DLL EntryPoint 的名称中追加字母“A”。

  大多数 Win32 API 使用这种追加“W”或“A”的约定。如果 CharSet 设置为 Auto,则这种转换就是与平台有关的(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。CharSet 的默认值为 Ansi。CharSet 字段也用于确定将从指定的 DLL 导入哪个版本的函数。

  CharSet.Ansi 和 CharSet.Unicode 的名称匹配规则大不相同。对于 Ansi 来说,如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethod”。如果 DLL 中没有“MyMethod”,但存在“MyMethodA”,则返回“MyMethodA”。

  对于 Unicode 来说则正好相反。如果将 EntryPoint 设置为“MyMethod”且它存在的话,则返回“MyMethodW”。如果 DLL 中不存在“MyMethodW”,但存在“MyMethod”,则返回“MyMethod”。如果使用的是 Auto,则匹配规则与平台有关(在 Windows NT 上为 Unicode,在 Windows 98 上为 Ansi)。如果 ExactSpelling 设置为 true,则只有当 DLL 中存在“MyMethod”时才返回“MyMethod”。

  3、EntryPoint 指示要调用的 DLL 入口点的名称或序号。
  如果你的方法名不想与api函数同名的话,一定要指定此参数,例如:



  [DllImport("user32.dll",CharSet="CharSet.Auto",EntryPoint="MessageBox")]
  public static extern int MsgBox(IntPtr hWnd,string txt,string caption, int type);

  4、ExactSpelling 指示是否应修改非托管 DLL 中的入口点的名称,以与 CharSet 字段中指定的 CharSet 值相对应。如果为 true,则当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Ansi 值时,向方法名称中追加字母 A,当 DllImportAttribute.CharSet 字段设置为 CharSet 的 Unicode 值时,向方法的名称中追加字母 W。此字段的默认值是 false。

  5、PreserveSig 指示托管方法签名不应转换成返回 HRESULT、并且可能有一个对应于返回值的附加 [out, retval] 参数的非托管签名。

  6、SetLastError 指示被调用方在从属性化方法返回之前将调用 Win32 API SetLastError。 true 指示调用方将调用 SetLastError,默认为 false。运行时封送拆收器将调用 GetLastError 并缓存返回的值,以防其被其他 API 调用重写。用户可通过调用 GetLastWin32Error 来检索错误代码。

;       *ppvObject = static_cast<IUnknown*>(this);
    else
        if(dwIID == IID_IClassFactory)
            *ppvObject = static_cast<IClassFactory*>(this);
        else
            return E_NOINTERFACE;
    reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
    return S_OK;
}
//—————————————————————————
HRESULT __stdcall TClassFactory::CreateInstance(IUnknown* pUnkownOuter,
        REFIID riid, void** ppvObj)
{
    if(pUnkownOuter != NULL)
        return CLASS_E_NOAGGREGATION;
    TCopyHook *pObj = new TCopyHook;
    pObj->AddRef();
    HRESULT hr = pObj->QueryInterface(riid, ppvObj);
    pObj->Release();
    return hr;
}
//—————————————————————————
HRESULT __stdcall TClassFactory::LockServer(BOOL fLock)
{
    if(fLock)
        nLocks++;
    else
        nLocks–;
    return S_OK;
}

2. 在BCB中New–>ActiveX–>ActiveX Library,然后添加相应代码。

   //—————————————————————-
   // 原作: webber84
   // 修改: ccrun(老妖)(www.ccrun.com)
   // 欢迎访问C++ Builder 研究:http://www.ccrun.com
   // 为防不负责任的转载者,此处加上原作者及修改者信息,请见谅。
   //—————————————————————-
   
   以下是修改后的Project1.cpp,大家可以直接copy过去。:
   
//$$—- axlib proj source —- (stAXLibProjectSource)
#define NO_WIN32_LEAN_AND_MEAN
#include <vcl.h>
#pragma hdrstop
#include <atl\atlvcl.h>
#include <objbase.h>
#include <olectl.h>
#include "TCopyHook.h"

#pragma package(smart_init)
TComModule  Project1Module;
TComModule &_Module = Project1Module;

// 这是要添加到注册表中的项,注意如果你要使用这段代码,应该用UUIDGEN.exe生成一
// 个新的CLSID。
const char* szRegTable[][3]=
{
    {"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}", 0, "CopyHook"},
    {"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\InProcServer32", 0, (const char*)-1},
    {"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\InProcServer32", "ThreadingModel", "Apartment"},
    {"CLSID\\{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}\\ProgID", 0, "webber84.CopyHook.1"},
    {"webber84.CopyHook.1", 0, "CopyHook"},
    {"webber84.CopyHook.1\\CLSID", 0, "{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735}"}
};

HMODULE hInstance = NULL;
LONG nLocks = 0;
//—————————————————————————
// The ATL Object map holds an array of _ATL_OBJMAP_ENTRY structures that
// described the objects of your OLE server. The MAP is handed to your
// project’s CComModule-derived _Module object via the Init method.
BEGIN_OBJECT_MAP(ObjectMap)
END_OBJECT_MAP()
//—————————————————————————
// Entry point of your Server invoked by Windows for processes or threads are
// initialized or terminated.
int WINAPI DllEntryPoint(HINSTANCE hinst, unsigned long reason, void*)
{
 if(reason == DLL_PROCESS_ATTACH)
        hInstance = (HMODULE)hinst;
    return TRUE;
}
//—————————————————————————
// _Module.Term is typically invoked from the DLL_PROCESS_DETACH of your
// DllEntryPoint. However, this may result in an incorrect shutdown sequence.
// Instead an Exit routine is setup to invoke the cleanup routine
// CComModule::Term.
void ModuleTerm(void)
{
    _Module.Term();
}
#pragma exit ModuleTerm 63
//—————————————————————————
// Entry point of your Server invoked to inquire whether the DLL is no
// longer in use and should be unloaded.
STDAPI __export DllCanUnloadNow(void)
{
    return nLocks == 0? S_OK: S_FALSE;
}
//—————————————————————————
// Entry point of your Server allowing OLE to retrieve a class object from
// your Server
STDAPI __export DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
{
    HRESULT hr = E_OUTOFMEMORY;
    *ppv = NULL;
    TClassFactory *pClassFactory = new TClassFactory;
    if(pClassFactory != NULL)
        hr = pClassFactory->QueryInterface(riid, ppv);
    return hr;
}
//—————————————————————————
// Entry point of your Server invoked to instruct the server to create
// registry entries for all classes supported by the module
STDAPI __export DllRegisterServer(void)
{
    HRESULT hr = S_OK;
    int nItems = sizeof(szRegTable) / sizeof(szRegTable[0]);
    char szDllPath[MAX_PATH];<BR>    GetModuleFileName(hInstance, szDllPath, MAX_PATH);
    for(int i=0; i<nItems && SUCCEEDED(hr); i++)
    {
        const char *szKeyName = szRegTable[i][0];
        const char *szValueName = szRegTable[i][1];
        const char *szValue = szRegTable[i][2];
        if(szValue == (const char*) – 1)
            szValue = szDllPath;
        HKEY hKey;
        LONG lReturn = RegCreateKey(HKEY_CLASSES_ROOT, szKeyName, &hKey);
        if(lReturn == ERROR_SUCCESS)
        {
            RegSetValueEx(hKey, szValueName, 0, REG_SZ,
                    (const BYTE*)szValue, strlen(szValue)+1);
            RegCloseKey(hKey);
        }
        if(lReturn != ERROR_SUCCESS)
        {
            hr = SELFREG_E_CLASS;
            DllUnregisterServer();
        }
    }
    return hr;
}
//—————————————————————————
// Entry point of your Server invoked to instruct the server to remove
// all registry entries created through DllRegisterServer.
STDAPI __export DllUnregisterServer(void)
{
    HRESULT hr = S_OK;
    LONG lReturn = 0;
    int nItems = sizeof(szRegTable) / sizeof(szRegTable[0]);
    for(int i=nItems-1; i>=0; i–)
    {
        const char *szKeyName = szRegTable[i][0];
        if((i == nItems-1) || stricmp(szRegTable[i+1][0], szKeyName) != 0)
            lReturn = RegDeleteKey(HKEY_CLASSES_ROOT, szKeyName);
        if(lReturn != ERROR_SUCCESS)
            hr = SELFREG_E_CLASS;
    }
    return hr;
}
//—————————————————————————

3. 在BCB的IDE环境中,选择菜单的Project–>Add to Project–>找到刚才创建的TCopyHook.cpp–>OK
   编译工程。如果没有错误,将生成Project1.dll。

4. 修改注册表:
   在HKEY_CLASSES_ROOT\Directory\shellex\CopyHookHandlers\下新建一个项,命名为Test,更改其默认值为{7e10a039-fe03-4f9c-b7e1-c5eeeaf53735},老妖注:这里的默认值应该和project1.cpp中的相同注册项目相同。
   
5. 注册COM组件:
   运行regsvr32.exe 路径\project.dll,点击确定后不要理会再弹出的错误窗口。重新启动计算机,试着copy一个文件夹,当paste的时候,效果出来了,嘿嘿。自己试试吧。

用拷贝钩子实现对文件夹的监控

作者:webber84       来源:
ICopyHook是一个用于创建拷贝钩子处理程序COM接口,它决定一个文件夹或者打印机对象是否可以被移动,拷贝,重命名或删除。Shell在执行这些操作之前,会调用ICopyHook接口的CopyCallback方法对它们进行验证。CopyCallback返回一个int值指示Shell是否应该继续执行这个操作。返回值IDYES表示继续,而返回值IDNO和IDCANCEL则表示终止。

一个文件夹对象可以安装多个拷贝钩子处理程序。如果出现这种情况,Shell会依次调用每个处理程序。只有当每个处理程序都返回IDYES时,Shell才真正执行用户请求的操作。

拷贝钩子处理程序的作用是在上述四种操作执行前对它们进行验证,但是Shell并不会把操作的结果通知给拷贝钩子处理程序。而windows提供的API函数FindFirstChangeNotification和FindNextChangeNotification却可以实现这个功能。因此,只有把这种两种方法结合起来,才能对一个文件夹的状态进行完全的监控。

拷贝钩子处理程序实现并不困难,首先创建一个作为进程内组件的COM对象,它只需要暴露一个ICopyHook接口(当然还有IUnknown)。然后用regsrv32.exe注册这个COM组件。最后一步是向Shell注册你的这个拷贝钩子处理程序,方法是在注册表HKEY_CLASSES_ROOT\Directory\Shellex\CopyHookHandlers下创建一个名称任意的sub key,在此sub key中创建一个类型为REG_SZ的项并将你的COM对象的CLSID作为它的默认值就可以了。

下面就是一个拷贝钩子的实现程序(注:以下代码经老妖改动并添加了详细操作过程,在BCB6中成功编译并通过测试)

1. 从ICopyHook接口创建TCopyHook,从IClassFactory接口创建TClassFactory:

// TCopyHook.h
// TCopyHook类实现了ICopyHook接口,TClassFactory实现了IClassFactory接口
//—————————————————————————
#define NO_WIN32_LEAN_AND_MEAN
#include <shlobj.h>
//—————————————————————————
class TCopyHook: public ICopyHook
{
public:
    TCopyHook():m_refcnt(0) {}
    STDMETHODIMP QueryInterface(REFIID iid,void **ppvObject);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP_(UINT) CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
            LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
            LPCTSTR pszDestFile, DWORD dwDestAttribs);
private:
    int m_refcnt;
};
//—————————————————————————
class TClassFactory : public IClassFactory
{
public:

    TClassFactory():m_refcnt(0) {}
    STDMETHODIMP QueryInterface(REFIID iid, void **ppvObject);
    STDMETHODIMP_(ULONG) AddRef();
    STDMETHODIMP_(ULONG) Release();
    STDMETHODIMP CreateInstance(IUnknown *pUnkOuter, REFIID riid, void **ppvObject);
    STDMETHODIMP LockServer(BOOL fLock);
private:
    int m_refcnt;
};


// TCopyHook.cpp
// TCopyHook对象和TClassFactory对象的实现文件
#include <stdio.h>
#include "TCopyHook.h"
//—————————————————————————
extern LONG nLocks;          // 对象计数,用于DllCanUnloadNow
ULONG __stdcall TCopyHook::AddRef()
{
    if(m_refcnt == 0)
        nLocks++;
    m_refcnt++;
    return m_refcnt;
}
//—————————————————————————
ULONG __stdcall TCopyHook::Release()
{
    int nNewCnt = –m_refcnt;
    if(nNewCnt <= 0)
    {
        nLocks–;
        delete this;
    }
    return nNewCnt;
}
//—————————————————————————
HRESULT __stdcall TCopyHook::QueryInterface(REFIID dwIID, void **ppvObject)
{
    if(dwIID == IID_IUnknown)
        *ppvObject = static_cast<IUnknown*>(this);
    else
        if(dwIID == IID_IShellCopyHook)
          &nbsp; *ppvObject = static_cast<ICopyHook*>(this);
        else
            return E_NOINTERFACE;
    reinterpret_cast<IUnknown*>(*ppvObject)->AddRef();
    return S_OK;
}
//—————————————————————————
// 这就是CopyCallback方法,拷贝钩子的所有功能由它实现。参数的具体值参看MSDN
UINT __stdcall TCopyHook::CopyCallback(HWND hwnd, UINT wFunc, UINT wFlags,
        LPCTSTR pszSrcFile, DWORD dwSrcAttribs,
        LPCTSTR pszDestFile, DWORD dwDestAttribs)
{
    char szMessage[MAX_PATH+14];
    sprintf(szMessage, "对%s进行的操作,是否继续?", pszSrcFile);
    return MessageBox(NULL, szMessage, "确认", MB_YESNO | MB_ICONEXCLAMATION);
}
//—————————————————————————
ULONG __stdcall TClassFactory::AddRef()
{
    if(m_refcnt==0)
        nLocks++;
    m_refcnt++;
    return m_refcnt;
}
//—————————————————————————
ULONG __stdcall TClassFactory::Release()
{

    int nNewCnt = –m_refcnt;

    if(nNewCnt <= 0)
    {
        nLocks–;
        delete this;
    }
    return nNewCnt;
}
//—————————————————————————
HRESULT __stdcall TClassFactory::QueryInterface(REFIID dwIID, void **ppvObject)
{
    if(dwIID == IID_IUnknown)

 一、概述

  Windows程序具有相对固定的结构,对编写者而言,不需要书写整个过程,大部分过程由系统完成。

  程序中只要按一定的格式填写系统留给客户的那一小部分。

  所需要完成的有:

  窗口类的定义、窗口的建立、消息函数的书写、消息循环。
 二、消息处理函数

  Windows程序是事件驱动的,对于一个窗口,它的大部分例行维护是由系统维护的。没个窗口都有一个消息处理函数。

  在消息处理函数中,对传入的消息进行处理。系统内还有它自己的缺省消息处理函数。

  客户写一个消息处理函数,在窗口建立前,将消息处理函数与窗口关联。这样,每当有消息产生时,就会去调用这个消息处理函数。

  通常情况下,客户都不会处理全部的消息,而是只处理自己感兴趣的消息,其他的,则送回到系统的缺省消息处理函数中去。



LRESULT CALLBACK WndProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case …

case …


}
return DefWindowProc (hwnd, message, wParam, lParam) ;
}


  三、窗口的建立

  客户需要自己建立窗口,建立后会得到系统返回的窗口句柄(HWND),后继的针对窗口的操作都针对句柄进行。

  1.注册窗口类

  建立窗口前,需要制定好这个窗口的相关属性,最主要的就是将自己定义的消息处理函数与窗口关联,其他的属性还包括:菜单、图标等等。

  这个属性指定步骤是通过指定"窗口类"来完成的。

  对于自己建立的窗口,这个"窗口类"需要自己制定,也即自己填充一个WNDCLASS结构,然后向系统注册。
对于一些特殊窗口,如按钮等控件,他们的行为是系统制定好了的,所以不需要自己注册,直接使用对应的“窗口类”名称就行了。

  2.建立窗口

  建立窗口时,注册的"窗口类"名称作为参数传入。

  这样,当有针对该窗口的消息时,将调用“窗口类”中指定的消息处理函数,在其中得到处理。

  四、消息循环

  系统会将针对这个程序的消息依次放到程序的“消息队列”中,由程序自己依次取出消息,在分发到对应的窗口中去。

  因此,建立窗口后,将进入一个循环。

  在循环中,取出消息、派发消息,循环往复,直到取得的消息是退出消息。

  循环退出后,程序即结束。


#include "stdafx.h"
#include <windows.h>

//一、消息处理函数
//参数:窗口句柄,消息,消息参数,消息参数
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
 //处理感兴趣的消息
 switch (message)
 {
  case WM_DESTROY:
   //当用户关闭窗口,窗口销毁,程序需结束,发退出消息,以退出消息循环
   PostQuitMessage (0) ;
   return 0 ;
 }
 //其他消息交给由系统提供的缺省处理函数
 return ::DefWindowProc (hwnd, message, wParam, lParam) ;
}

//二、应用程序主函数
//参数:实例句柄、前一个实例的句柄、命令行参数、窗口显示方式
int WINAPI WinMain (HINSTANCE hInstance, HINSTANCE hPrevInstance,
PSTR szCmdLine, int iCmdShow)
{
 //1.注册窗口类
 static TCHAR szAppName[] = TEXT ("HelloWin") ; //窗口类名称
 //定制"窗口类"结构
 WNDCLASS wndclass ;
 wndclass.style = CS_HREDRAW | CS_VREDRAW ;
 wndclass.lpfnWndProc = WndProc ; //关联消息处理函数
 wndclass.cbClsExtra = 0 ;
 wndclass.cbWndExtra = 0 ;
 wndclass.hInstance = hInstance ; //实例句柄
 wndclass.hIcon = LoadIcon (NULL, IDI_APPLICATION) ; //图标
 wndclass.hCursor = LoadCursor (NULL, IDC_ARROW) ; //光标
 wndclass.hbrBackground = (HBRUSH) GetStockObject (WHITE_BRUSH); //画刷
 wndclass.lpszMenuName = NULL ;
 wndclass.lpszClassName = szAppName; //类名称
 //注册
 if (!RegisterClass (&wndclass))
 {
  MessageBox (NULL, TEXT ("RegisterClass Fail!"),
  szAppName, MB_ICONERROR) ;
  return 0 ;
 }

 //建立窗口
 HWND hwnd ;
 hwnd = CreateWindow (szAppName, //窗口类名称
  TEXT ("The Hello Program"), //窗口标题
  WS_OVERLAPPEDWINDOW, //窗口风格
  CW_USEDEFAULT,
  CW_USEDEFAULT,
  CW_USEDEFAULT,
  CW_USEDEFAULT,
  NULL,
  NULL,
  hInstance, //实例句柄
  NULL);

 ShowWindow (hwnd, iCmdShow) ;
 UpdateWindow (hwnd) ;
 
 //消息循环
 MSG msg ;
 while (GetMessage (&msg, NULL, 0, 0)) //从消息队列中取消息
 {
  TranslateMessage (&msg) ; //转换消息
  DispatchMessage (&msg) ; //派发消息
 }
 return msg.wParam ;
}

基于Visual C++的钩子编程技巧

作者:       来源:

 钩子(Hook)是Windows消息处理机制的一个要点(Point)。应用程序可以通过钩子机制截获处理Window消息或是其他一些特定事件。同DOS中断截获处理机制类似,应用程序可以在钩子上设置多个钩子函数,由其组成一个与钩子相关联的指向钩子函数的指针列表(钩子链表)。当钩子所监视的消息出现时,Windows首先将其送到调用链表中所指向的第一个钩子函数中,钩子函数将根据其各自的功能对消息进行监视、修改和控制,并在处理完成后把消息传递给下一钩子函数直至到达钩子链表的末尾。在钩子函数交出控制权后,被拦截的消息最终仍将交还给窗口处理函数。虽然钩子函数对消息的过滤将会略加影响系统的运行效率,但在很多场合下通过钩子对消息的过滤处理可以完成一些其他方法所不能完成的特殊功能。

  可以看出,钩子的本质是一段用以处理系统消息或特定事件的函数,通过系统调用将其挂入到系统。钩子的种类有很多,每一种钩子负责截获并处理相应的消息。钩子机制允许应用程序截获并处理发往指定窗口的消息或特定事件,其监视的窗口即可以是本进程内的也可以是由其他进程所创建的。在特定的消息发出后、达目的窗口前,钩子程序拥有对其控制权,此时的钩子函数除了可以对截获的消息进行各种处理外,甚至还可以强行终止消息的继续传递。

  对于多个钩子的安装,最近安装的钩子将被放置于钩子链的开始,最早安装的钩子则放在最后,在钩子监视的消息出现时,操作系统调用链表开始处的第一个钩子函数进行处理,也就是说最后加入的钩子优先获得控制权。这里提到的钩子函数必须是一个回调函数,而且不能定义为类成员函数,只能是普通的C函数,如:

  LRESULT CALLBACK HookProc(int nCode ,WPARAM wParam,LPARAM lParam);

  线程局部钩子与系统全局钩子

  钩子根据其对消息监视范围的不同而分为系统全局钩子和线程局部钩子两大类,其中线程局部钩子只能监视本进程中某个指定的线程,而全局钩子则可对在当前系统下运行的所有线程进行监视。显然,线程钩子可以看作是全局钩子的一个子集,全局钩子虽然功能强大但同时实现起来也比较烦琐:其钩子函数的实现必须封装在独立的动态链接库中才可以被各种相关联的应用程序所使用。

  虽然对于线程局部钩子并不要求其象系统全局钩子一样必须放置于动态链接库中,但是推荐的做法仍是将其放到动态链接库中去实现。这样的处理不仅能使钩子为系统内的多个进程所访问,同时也可以在系统中被直接调用。对于一个只供单进程访问的钩子,还可以将其钩子处理过程放在安装钩子的同一个线程内。
  钩子的安装与卸载

  系统是通过调用位于钩子链表最开始处的钩子函数而进行消息拦截处理的,因此在设置钩子时要把回调函数放置于钩子链表的链首,操作系统会使其首先被调用。由函数SetWindowsHookEx()负责将回调函数放置于钩子链表的开始位置。SetWindowsHookEx()函数原型声明为:

HHOOK SetWindowsHookEx(int idHook;HOOKPROC lpfn;HINSTANCE hMod;DWORD dwThreadId);

  其中,参数idHook 指定了钩子的类型,可以使用的类型有以下13种:

WH_CALLWNDPROC 系统将消息发送到指定窗口之前的“钩子”
WH_CALLWNDPROCRET 消息已经在窗口中处理的“钩子”
WH_CBT 基于计算机培训的“钩子”
WH_DEBUG 差错“钩子”
WH_FOREGROUNDIDLE 前台空闲窗口“钩子”
WH_GETMESSAGE 接收消息投递的“钩子”
WH_JOURNALPLAYBACK 回放以前通过WH_JOURNALRECORD“钩子”记录的输入消息
WH_JOURNALRECORD 输入消息记录“钩子”
WH_KEYBOARD 键盘消息“钩子”
WH_MOUSE 鼠标消息“钩子”
WH_MSGFILTER 对话框、消息框、菜单或滚动条输入消息“钩子”
WH_SHELL 外壳“钩子”
WH_SYSMSGFILTER 系统消息“钩子” 

  参数lpfn为指向钩子函数的指针,也即回调函数的首地址;参数hMod标识了钩子处理函数所处模块的句柄;参数dwThreadId 指定被监视的线程,如果明确指定了某个线程的ID就只监视该线程,此时的钩子即为线程钩子;如果该参数被设置为0,则表示此钩子为监视系统所有线程的

全局钩子。此函数在执行完后将返回一个钩子句柄。

  在SetWindowsHookEx()函数完成对钩子的安装后,如果被监视的事件发生,系统会立即调用位于相应钩子链表开始处的钩子函数进行处理,每一个钩子函数在进行处理时都要考虑是否需要把事件传递给下一个钩子处理函数。如果需要传递,就要调用函数CallNestHookEx()。尽管在理论上不调用CallNestHookEx()也并不算错,但在实际使用时还是强烈建议无论是否需要进行事件传递都要在过程的最后调用一次CallNextHookEx( ),否则将会引起一些无法预知的系统行为或是系统锁定。该函数将返回位于钩子链表中的下一个钩子处理过程的地址,至于具体的返回值类型则要视所设置的钩子类型而定。CallNextHookEx( )的函数原型为:

LRESULT CallNextHookEx(HHOOK hhk;int nCode;WPARAM wParam;LPARAM lParam);

  其中,参数hhk为由SetWindowsHookEx()函数返回的当前钩子句柄;参数nCode为传给钩子过程的事件代码;参数wParam和lParam 则为传给钩子处理函数的参数值,其具体含义同设置的钩子类型有关。

  由于安装钩子对系统的性能有一定的影响,所以在钩子使用完毕后应及时将其卸载以释放其所占资源。释放钩子的函数为UnhookWindowsHookEx(),该函数比较简单只有一个参数用于指定此前由SetWindowsHookEx()函数所返回的钩子句柄,原型声明如下:

BOOL UnhookWindowsHookEx(HHOOK hhk);

  使用鼠标钩子

  由于系统全局钩子在功能上完全覆盖了线程局部钩子,因此其实际使用范围要远比线程局部钩子广泛的多。本节也由此着重对系统全局钩子的使用进行介绍。

  鼠标钩子是钩子中比较常用也是使用比较简单的一类钩子。下面给出的应用示例将通过安装鼠标全局钩子来捕获鼠标当前所处窗口的窗口标题。由于本例程使用了全局钩子,因此首先构造全局钩子的载体——动态链接库。考虑到 Win32 DLL与Win16 DLL存在的差别,在Win32环境下要在多个进程间共享数据,就必须采取一些措施将待共享的数据提取到一个独立的数据段,并通过def文件将其属性设置为读写共享:

#pragma data_seg("mydata")
HWND glhPrevTarWnd = NULL; // 上次鼠标所指的窗口句柄
HWND glhDisplayWnd = NULL; // 显示目标窗口标题编辑框的句柄
HWND glhHook = NULL; // 安装的鼠标钩子句柄
HINSTANCE glhInstance = NULL; // DLL实例句柄
#pragma data_seg()
……
SECTIONS // def文件中将数据段TestData设置为读写共享
TestData READ WRITE SHARED

  在完成上述准备工作后,在动态库输出函数StartHook()中调用SetWindowsHookEx()函数完成对全局鼠标钩子的安装,设定鼠标钩子函数为MouseProc(),安装函数返回的钩子句柄保存于变量glhHook中:

BOOL CMouseHook::StartHook(HWND hWnd)
{
 BOOL result = FALSE;
 // 安装钩子
 glhHook = (HWND)SetWindowsHookEx(WH_MOUSE, MouseProc, glhInstance, 0);
 if (glhHook != NULL)
  result = TRUE;
 glhDisplayWnd = hWnd; // 设置显示目标窗口标题编辑框的句柄
 return result;
}

  在鼠标钩子安装完毕后,系统内的任何鼠标动作所发出的鼠标消息均要经过钩子函数MouseProc()的拦截过滤处理。在此进行的处理是通过获取当前鼠标所在位置下的窗口句柄,并以此进一步得到窗口标题。在处理完成后,调用CallNextHookEx()函数将本事件传递到钩子链表中的下一个钩子函数:

LRESULT WINAPI MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
 LP

MOUSEHOOKSTRUCT pMouseHook = (MOUSEHOOKSTRUCT FAR *) lParam;
 if (nCode >= 0) {
  HWND glhTargetWnd = pMouseHook->hwnd; // 取目标窗口句柄
  HWND ParentWnd = glhTargetWnd;
  while (ParentWnd != NULL){
   glhTargetWnd = ParentWnd;
   ParentWnd = GetParent(glhTargetWnd); // 取应用程序主窗口句柄
  }
  if (glhTargetWnd != glhPrevTarWnd) {
   char szCaption[100];
   GetWindowText(glhTargetWnd, szCaption, 100); // 取目标窗口标题
   if (IsWindow(glhDisplayWnd))
    SendMessage(glhDisplayWnd, WM_SETTEXT, 0, (LPARAM)(LPCTSTR)szCaption);
   glhPrevTarWnd = glhTargetWnd; // 保存目标窗口
  }
 }
 // 继续传递消息
 return CallNextHookEx((HHOOK)glhHook, nCode, wParam, lParam);
}

  此动态链接库还提供有输出函数StopHook(),调用程序通过对此函数的调用完成对先前加载钩子的卸载。在此输出函数内则是通过UnhookWindowsHookEx()函数来卸载指定钩子的:

BOOL CMouseHook::StopHook()
{
 BOOL result = FALSE;
 if (glhHook){
  result = UnhookWindowsHookEx((HHOOK)glhHook); // 卸载钩子
  if (result)
   glhDisplayWnd = glhPrevTarWnd = glhHook = NULL;
 }
 return result;
}

  通过编译、链接可以得到有关鼠标全局钩子的动态链接库,在经过调用程序对其的调用后,可以实现对在当前系统所有线程中的鼠标消息的拦截处理。在钩子动态链接库加载到进程后,只需调用输出函数StartHook()安装好全局钩子即可对鼠标消息进行拦截过滤,在调用程序退出前调用输出函数StopHook()卸载钩子。
  使用键盘钩子

  键盘钩子同鼠标钩子一样,也是一类较常用的钩子。而且总的来说键盘钩子的使用与鼠标钩子的使用也是比较类似的,下面通过一个程序实例来对键盘钩子的使用进行介绍,在此例程中通过设置键盘全局钩子来记录当前系统输入的字符,并将拦截到的字符按日期保存到文件。

  由于本例程也使用的系统全局钩子,因此由调用程序和动态链接库程序两部分组成。动态链接库程序提供了两个输出函数InstallLaunchEv()和UnInstall(),分别用于键盘钩子的安装与卸载:

DllExport void WINAPI InstallLaunchEv()
{
 glhHook = (HHOOK)SetWindowsHookEx(WH_KEYBOARD, (HOOKPROC)LauncherHook, theApp.m_hInstance, 0); // 安装键盘钩子
}
DllExport void WINAPI UnInstall()
{
 if (glhHook) {
  BOOL result = UnhookWindowsHookEx((HHOOK)glhHook); // 卸载钩子
  if (result)
   glhHook = NULL;
 }

  在钩子安装函数中,通过SetWindowsHookEx()设置了键盘钩子函数LauncherHook()。在键盘事件发生后此钩子函数将被调用,通过SaveLog()函数将捕获到的字符保存到文件:

LRESULT CALLBACK LauncherHook(int nCode, WPARAM wParam, LPARAM lParam)
{
 // 将键盘事件传递给下一钩子函数
 LRESULT Result = CallNextHookEx(glhHook, nCode, wParam, lParam);
 if (nCode == HC_ACTION) // 处理键盘动作
 {
  if (lParam & 0×80000000) // 保存键盘动作到文件
  {
   char c[1];
   c[0] = wParam;
   SaveLog(c);
   if (c[0] == 13) {
    c[0] = 10;
    SaveLog(c);
   }
  }
 }
 return Result;
}

  至于调用程序则非常简单,只是在程序开始运行后和在程序退出前分别调用动态链接库提供的钩子设置函数和钩子卸载函数即可。

  钩子的种类有很多,从前面给出的鼠标钩子和键盘钩子也可以看出其编写过程是非常类似的。通常情况下作为钩子载体的动态链接库要提供有两个必要的输出函数——钩子安装函数和钩子卸载函数。在钩子安装函数中通过SetWindowsHookEx()为不同类型的钩子设置相应的钩子函数,实质性的工作也主要是在钩子函数中完成的,具体情况应视钩子的类型和要完成的功能而定。在钩子卸载函数中则主要是对UnhookWindowsHookEx()的调用,并辅以必要的保护性代码。

另一种容器——集合(set)

这是微软帮助文档中对集合(set)的解释:“描述了一个控制变长元素序列的对象(注:set中的key和value是Key类型的,而map中的key和value是一个pair结构中的两个分量)的模板类,每一个元素包含了一个排序键(sort key)和一个值(value)。对这个序列可以进行查找、插入、删除序列中的任意一个元素,而完成这些操作的时间同这个序列中元素个数的对数成比例关系,并且当游标指向一个已删除的元素时,删除操作无效。”
而一个经过更正的和更加实际的定义应该是:一个集合(set)是一个容器,它其中所包含的元素的值是唯一的。这在收集一个数据的具体值的时候是有用的。集合中的元素按一定的顺序排列,并被作为集合中的实例。如果你需要一个键/值对(pair)来存储数据,map是一个更好的选择。一个集合通过一个链表来组织,在插入操作和删除操作上比向量(vector)快,但查找或添加末尾的元素时会有些慢。
下面是一个例子:

//程序:set演示
//目的:理解STL中的集合(set)

#include <string>
#include <set>
#include <iostream>
using namespace std;

int main(int argc, char* argv[])
{
  set <string> strset;
  set <string>::iterator si;
  strset.insert("cantaloupes");
  strset.insert("apple");
  strset.insert("orange");
  strset.insert("banana");
  strset.insert("grapes");
  strset.insert("grapes"); 
  for (si=strset.begin(); si!=strset.end(); si++) 
  {  cout << *si << " ";  }
  cout << endl;
  return 0;
}

// 输出: apple banana cantaloupes grapes orange
//注意:输出的集合中的元素是按字母大小顺序排列的,而且每个值都不重复。

如果你感兴趣的话,你可以将输出循环用下面的代码替换:

copy(strset.begin(), strset.end(), ostream_iterator<string>(cout, " "));

.集合(set)虽然更强大,但我个人认为它有些不清晰的地方而且更容易出错,如果你明白了这一点,你会知道用集合(set)可以做什么。

所有的STL容器

容器(Container)的概念的出现早于模板(template),它原本是一个计算机科学领域中的一个重要概念,但在这里,它的概念和STL混合在一起了。下面是在STL中出现的7种容器:

vector(向量)——STL中标准而安全的数组。只能在vector 的“前面”增加数据。
deque(双端队列double-ended queue)——在功能上和vector相似,但是可以在前后两端向其中添加数据。
list(列表)——游标一次只可以移动一步。如果你对链表已经很熟悉,那么STL中的list则是一个双向链表(每个节点有指向前驱和指向后继的两个指针)。
set(集合)——包含了经过排序了的数据,这些数据的值(value)必须是唯一的。
map(映射)——经过排序了的二元组的集合,map中的每个元素都是由两个值组成,其中的key(键值,一个map中的键值必须是唯一的)是在排序或搜索时使用,它的值可以在容器中重新获取;而另一个值是该元素关联的数值。比如,除了可以ar[43] = "overripe"这样找到一个数据,map还可以通过ar["banana"] = "overripe"这样的方法找到一个数据。如果你想获得其中的元素信息,通过输入元素的全名就可以轻松实现。
multiset(多重集)——和集合(set)相似,然而其中的值不要求必须是唯一的(即可以有重复)。
multimap(多重映射)——和映射(map)相似,然而其中的键值不要求必须是唯一的(即可以有重复)。
注意:如果你阅读微软的帮助文档,你会遇到对每种容器的效率的陈述。比如:log(n*n)的插入时间。除非你要处理大量的数据,否则这些时间的影响是可以忽略的。如果你发现你的程序有明显的滞后感或者需要处理时间攸关(time critical)的事情,你可以去了解更多有关各种容器运行效率的话题。

怎样在一个map中使用类?

Map是一个通过key(键)来获得value(值)的模板类。
另一个问题是你希望在map中使用自己的类而不是已有的数据类型,比如现在已经用过的int。建立一个“为模板准备的(template-ready)”类,你必须确保在该类中包含一些成员函数和重载操作符。下面的一些成员是必须的:

  • 缺省的构造函数(通常为空)
  • 拷贝构造函数
  • 重载的”=”运算符
     

你应该重载尽可能多的运算符来满足特定模板的需要,比如,如果你想定义一个类作为 map中的键(key),你必须重载相关的运算符。但在这里不对重载运算符做过多讨论了。

//程序:映射自定义的类。
//目的:说明在map中怎样使用自定义的类。

#include <string>
#include <iostream>
#include <vector>
#include <map>
using namespace std;

class CStudent
{
public :
  int nStudentID;
  int nAge;
public :
  //缺省构造函数——通常为空
  CStudent()  {  }
  // 完整的构造函数
  CStudent(int nSID, int nA)  {  nStudentID=nSID; nAge=nA;  }
  //拷贝构造函数
  CStudent(const CStudent& ob) 
    {  nStudentID=ob.nStudentID; nAge=ob.nAge;  }
  // 重载“=”
  void operator = (const CStudent& ob) 
    {  nStudentID=ob.nStudentID; nAge=ob.nAge;  }
};

int main(int argc, char* argv[])
{
  map <string, CStudent> mapStudent;

  mapStudent["Joe Lennon"] = CStudent(103547, 22);
  mapStudent["Phil McCartney"] = CStudent(100723, 22);
  mapStudent["Raoul Starr"] = CStudent(107350, 24);
  mapStudent["Gordon Hamilton"] = CStudent(102330, 22);

  // 通过姓名来访问Cstudent类中的成员
  cout << "The Student number for Joe Lennon is " <<
    (mapStudent["Joe Lennon"].nStudentID) << endl;

  return 0;
}


TYPEDEF

如果你喜欢使用typedef关键字,下面是个例子:
typedef set <int> SET_INT;
typedef SET_INT::iterator SET_INT_ITER

编写代码的一个习惯就是使用大写字母和下划线来命名数据类型。

ANSI / ISO字符串

ANSI/ISO字符串在STL容器中使用得很普遍。这是标准的字符串类,并得到了广泛地提倡,然而在缺乏格式声明的情况下就会出问题。你必须使用“<<”和输入输出流(iostream)代码(如dec, width等)将字符串串联起来。
可在必要的时候使用c_str()来重新获得字符指针。

游标(Iterator)

我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游标是通过重载一元的”*”和”->”来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标可以看作是句柄(handle)。通常情况下游标(iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变:
iterator——对于除了vector以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用“++”操作符。而不能使用“–”或“+=”操作符。而对于vector这一种容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一种操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比较运算符。
reverse_iterator ——如果你想用向后的方向而不是向前的方向的游标来遍历除vector之外的容器中的元素,你可以使用reverse_iterator 来反转遍历的方向,你还可以用rbegin()来代替begin(),用rend()代替end(),而此时的“++”操作符会朝向后的方向遍历。
const_iterator ——一个向前方向的游标,它返回一个常数值。你可以使用这种类型的游标来指向一个只读的值。
const_reverse_iterator ——一个朝反方向遍历的游标,它返回一个常数值。

Set和Map中的排序

除了类型和值外,模板含有其他的参数。你可以传递一个回调函数(通常所说的声明“predicate”——这是带有一个参数的函数返回一个布尔值)。例如,如果你想自动建立一个集合,集合中的元素按升序排列,你可以用简明的方法建立一个set类:

set <int, greater<int> > set1

greater 是另一个模板函数(范型函数),当值放置在容器中后,它用来为这些值排序。如果你想按降序排列这些值,你可以这样写:

set <int, less<int> > set1

在实现算法时,将声明(predicate)作为一个参数传递到一个STL模板类中时会遇到很多的其他情况,下面将会对这些情况进行详细描述。

STL 的烦恼之二——错误信息

这些模板的命名需要对编译器进行扩充,所以当编译器因某种原因发生故障时,它会列出一段很长的错误信息,并且这些错误信息晦涩难懂。我觉得处理这样的难题没有什么好办法。但最好的方法是去查找并仔细研究错误信息指明代码段的尾端。还有一个烦恼就是:当你双击错误信息时,它会将错误指向模版库的内部代码,而这些代码就更难读了。一般情况下,纠错的最好方法是重新检查一下你的代码,运行时忽略所有的警告信息。

算法(Algorithms)

算法是模板中使用的函数。这才真正开始体现STL的强大之处。你可以学习一些大多数模板容器中都会用到的一些算法函数,这样你可以通过最简便的方式进行排序、查找、交换等操作。STL中包含着一系列实现算法的函数。比如:sort(vec.begin()+1, vec.end()-1)可以实现对除第一个和最后一个元素的其他元素的排序操作。
容器自身不能使用算法,但两个容器中的游标可以限定容器中使用算法的元素。既然这样,算法不直接受到容器的限制,而是通过采用游标,算法才能够得到支持。此外,很多次你会遇到传递一个已经准备好了的函数(以前提到的声明:predicate)作为参数,你也可以传递以前的旧值。
下面的例子演示了怎样使用算法:

//程序:测试分数统计
//目的:通过对向量中保存的分数的操作说明怎样使用算法

#include <algorithm>  //如果要使用算法函数,你必须要包含这个头文件。
#include <numeric>  // 包含accumulate(求和)函数的头文件
#include <vector>
#include <iostream>
using namespace std;

int testscore[] = {67, 56, 24, 78, 99, 87, 56};

//判断一个成绩是否通过了考试
bool passed_test(int n)
{
  return (n >= 60);
}

// 判断一个成绩是否不及格
bool failed_test(int n)
{
  return (n < 60);
}

int main(int argc, char* argv[])
{
  int total;
  // 初始化向量,使之能够装入testscore数组中的元素
  vector <int> vecTestScore(testscore,
     testscore + sizeof(testscore) / sizeof(int));
  vector <int>::iterator vi;

  // 排序并显示向量中的数据
  sort(vecTestScore.begin(), vecTestScore.end());
  cout << "Sorted Test Scores:" << endl;
  for (vi=vecTestScore.begin(); vi != vecTestScore.end(); vi++)
  {  cout << *vi << ", ";  }
  cout << endl;

  // 显示统计信息

  // min_element 返回一个 _iterator_ 类型的对象,该对象指向值最小的那个元素。
  //“*”运算符提取元素中的值。
  vi = min_element(vecTestScore.begin(), vecTestScore.end());
  cout << "The lowest score was " << *vi << "." << endl;

  //与min_element类似,max_element是选出最大值。
  vi = max_element(vecTestScore.begin(), vecTestScore.end());
  cout << "The highest score was " << *vi << "." << endl;

  // 使用声明函数(predicate function,指vecTestScore.begin()和vecTestScore.end())来确定通过考试的人数。
  cout << count_if(vecTestScore.begin(), vecTestScore.end(), passed_test) <<
    " out of " << vecTestScore.size() <<
    " students passed the test" << endl;

  // 确定有多少人考试挂了
  cout << count_if(vecTestScore.begin(),
    vecTestScore.end(), failed_test) <<
    " out of " << vecTestScore.size() <<
    " students failed the test" << endl;

  //计算成绩总和
  total = accumulate(vecTestScore.begin(),
     vecTestScore.end(), 0);
  // 计算显示平均成绩
  cout << "Average score was " <<
    (total / (int)(vecTestScore.size())) << endl;

  return 0;
}

Allocator(分配器)

Allocator用在模板的初始化阶段,是为对象和数组进行分配内存空间和释放空间操作的模板类。它在各种情况下扮演着很神秘的角色,它关心的是高层内存的优化,而且对黑盒测试来说,使用Allocator是最好的选择。通常,我们不需要明确指明它,因为它们通常是作为不用添加的缺省的参数出现的。如果在专业的测试工作中出现了Allocator,你最好搞清楚它是什么。

Embed Templates(嵌入式模版)和Derive Templates(基模板)

每当你使用一个普通的类的时候,你也可以在其中使用一个STL类。它是可以被嵌入的:

class CParam
{
  string name;
  string unit;
  vector <double> vecData;
};

或者将它作为一个基类:

class CParam : public vector <double>
{
  string name;
  string unit;
};

STL模版类作为基类时需要谨慎。这需要你适应这种编程方式。

模版中的模版

为构建一个复杂的数据结构,你可以将一个模板植入另一个模板中(即“模版嵌套”)。一般最好的方法是在程序前面使用typedef关键字来定义一个在另一个模板中使用的模版类型。

// 程序:在向量中嵌入向量的演示。
//目的:说明怎样使用嵌套的STL容器。

#include <iostream>
#include <vector>

using namespace std;

typedef vector <int> VEC_INT;

int inp[2][2] = {{1, 1}, {2, 0}}; 
  // 要放入模板中的2×2的正则数组

int main(int argc, char* argv[])
{
  int i, j;
  vector <VEC_INT> vecvec;
  // 如果你想用一句话实现这样的嵌套,你可以这样写:
  // vector <vector <int> > vecvec;
 
  // 将数组填入向量
  VEC_INT v0(inp[0], inp[0]+2); 
    // 传递两个指针
    // 将数组中的值拷贝到向量中
  VEC_INT v1(inp[1], inp[1]+2);

  vecvec.push_back(v0);
  vecvec.push_back(v1);

  for (i=0; i<2; i++)
  {
    for (j=0; j<2; j++)
    {
      cout << vecvec[i][j] << "  ";
    }
    cout << endl;
  }
  return 0;
}

// 输出:
// 1 1
// 2 0

虽然在初始化时很麻烦,一旦你将数据填如向量中,你就实现了一个变长的可扩充的二维数组(大小可扩充直到使用完内存)。根据实际需要,可以使用各种容器的嵌套组合。

总结

STL是有用的,但是使用过程中的困难和麻烦是再所难免的。就像中国人所说的:“如果你掌握了它,便犹如虎添翼。”

相关链接:
Josuttis Website :http://www.josuttis.com/
Pretty Good Initialization Library :http://www.codeproject.com/vcpp/stl/PGIL.asp


TYPEDEF

如果你喜欢使用typedef关键字,下面是个例子:
typedef set <int> SET_INT;
typedef SET_INT::iterator SET_INT_ITER

编写代码的一个习惯就是使用大写字母和下划线来命名数据类型。

ANSI / ISO字符串

ANSI/ISO字符串在STL容器中使用得很普遍。这是标准的字符串类,并得到了广泛地提倡,然而在缺乏格式声明的情况下就会出问题。你必须使用“<<”和输入输出流(iostream)代码(如dec, width等)将字符串串联起来。
可在必要的时候使用c_str()来重新获得字符指针。

游标(Iterator)

我说过游标是指针,但不仅仅是指针。游标和指针很像,功能很像指针,但是实际上,游标是通过重载一元的”*”和”->”来从容器中间接地返回一个值。将这些值存储在容器中并不是一个好主意,因为每当一个新值添加到容器中或者有一个值从容器中删除,这些值就会失效。在某种程度上,游标可以看作是句柄(handle)。通常情况下游标(iterator)的类型可以有所变化,这样容器也会有几种不同方式的转变:
iterator——对于除了vector以外的其他任何容器,你可以通过这种游标在一次操作中在容器中朝向前的方向走一步。这意味着对于这种游标你只能使用“++”操作符。而不能使用“–”或“+=”操作符。而对于vector这一种容器,你可以使用“+=”、“—”、“++”、“-=”中的任何一种操作符和“<”、“<=”、“>”、“>=”、“==”、“!=”等比较运算符。
reverse_iterator ——如果你想用向后的方向而不是向前的方向的游标来遍历除vector之外的容器中的元素,你可以使用reverse_iterator 来反转遍历的方向,你还可以用rbegin()来代替begin(),用rend()代替end(),而此时的“++”操作符会朝向后的方向遍历。
const_iterator ——一个向前方向的游标,它返回一个常数值。你可以使用这种类型的游标来指向一个只读的值。
const_reverse_iterator ——一个朝反方向遍历的游标,它返回一个常数值。

Set和Map中的排序

除了类型和值外,模板含有其他的参数。你可以传递一个回调函数(通常所说的声明“predicate”——这是带有一个参数的函数返回一个布尔值)。例如,如果你想自动建立一个集合,集合中的元素按升序排列,你可以用简明的方法建立一个set类:

set <int, greater<int> > set1

greater 是另一个模板函数(范型函数),当值放置在容器中后,它用来为这些值排序。如果你想按降序排列这些值,你可以这样写:

set <int, less<int> > set1

在实现算法时,将声明(predicate)作为一个参数传递到一个STL模板类中时会遇到很多的其他情况,下面将会对这些情况进行详细描述。

STL 的烦恼之二——错误信息

这些模板的命名需要对编译器进行扩充,所以当编译器因某种原因发生故障时,它会列出一段很长的错误信息,并且这些错误信息晦涩难懂。我觉得处理这样的难题没有什么好办法。但最好的方法是去查找并仔细研究错误信息指明代码段的尾端。还有一个烦恼就是:当你双击错误信息时,它会将错误指向模版库的内部代码,而这些代码就更难读了。一般情况下,纠错的最好方法是重新检查一下你的代码,运行时忽略所有的警告信息。

算法(Algorithms)

算法是模板中使用的函数。这才真正开始体现STL的强大之处。你可以学习一些大多数模板容器中都会用到的一些算法函数,这样你可以通过最简便的方式进行排序、查找、交换等操作。STL中包含着一系列实现算法的函数。比如:sort(vec.begin()+1, vec.end()-1)可以实现对除第一个和最后一个元素的其他元素的排序操作。
容器自身不能使用算法,但两个容器中的游标可以限定容器中使用算法的元素。既然这样,算法不直接受到容器的限制,而是通过采用游标,算法才能够得到支持。此外,很多次你会遇到传递一个已经准备好了的函数(以前提到的声明:predicate)作为参数,你也可以传递以前的旧值。
下面的例子演示了怎样使用算法:

//程序:测试分数统计
//目的:通过对向量中保存的分数的操作说明怎样使用算法

#include <algorithm>  //如果要使用算法函数,你必须要包含这个头文件。
#include <numeric>  // 包含accumulate(求和)函数的头文件
#include <vector>
#include <iostream>
using namespace std;

int testscore[] = {67, 56, 24, 78, 99, 87, 56};

//判断一个成绩是否通过了考试
bool passed_test(int n)
{
  return (n >= 60);
}

// 判断一个成绩是否不及格
bool failed_test(int n)
{
  return (n < 60);
}

int main(int argc, char* argv[])
{
  int total;
  // 初始化向量,使之能够装入testscore数组中的元素
  vector <int> vecTestScore(testscore,
     testscore + sizeof(testscore) / sizeof(int));
  vector <int>::iterator vi;

  // 排序并显示向量中的数据
  sort(vecTestScore.begin(), vecTestScore.end());
  cout << "Sorted Test Scores:" << endl;
  for (vi=vecTestScore.begin(); vi != vecTestScore.end(); vi++)
  {  cout << *vi << ", ";  }
  cout << endl;

  // 显示统计信息

  // min_element 返回一个 _iterator_ 类型的对象,该对象指向值最小的那个元素。
  //“*”运算符提取元素中的值。
  vi = min_element(vecTestScore.begin(), vecTestScore.end());
  cout << "The lowest score was " << *vi << "." << endl;

  //与min_element类似,max_element是选出最大值。
  vi = max_element(vecTestScore.begin(), vecTestScore.end());
  cout << "The highest score was " << *vi << "." << endl;

  // 使用声明函数(predicate function,指vecTestScore.begin()和vecTestScore.end())来确定通过考试的人数。
  cout << count_if(vecTestScore.begin(), vecTestScore.end(), passed_test) <<
    " out of " << vecTestScore.size() <<
    " students passed the test" << endl;

  // 确定有多少人考试挂了
  cout << count_if(vecTestScore.begin(),
    vecTestScore.end(), failed_test) <<
    " out of " << vecTestScore.size() <<
    " students failed the test" << endl;

  //计算成绩总和
  total = accumulate(vecTestScore.begin(),
     vecTestScore.end(), 0);
  // 计算显示平均成绩
  cout << "Average score was " <<
    (total / (int)(vecTestScore.size())) << endl;

  return 0;
}

Allocator(分配器)

Allocator用在模板的初始化阶段,是为对象和数组进行分配内存空间和释放空间操作的模板类。它在各种情况下扮演着很神秘的角色,它关心的是高层内存的优化,而且对黑盒测试来说,使用Allocator是最好的选择。通常,我们不需要明确指明它,因为它们通常是作为不用添加的缺省的参数出现的。如果在专业的测试工作中出现了Allocator,你最好搞清楚它是什么。

Embed Templates(嵌入式模版)和Derive Templates(基模板)

每当你使用一个普通的类的时候,你也可以在其中使用一个STL类。它是可以被嵌入的:

class CParam
{
  string name;
  string unit;
  vector <double> vecData;
};

或者将它作为一个基类:

class CParam : public vector <double>
{
  string name;
  string unit;
};

STL模版类作为基类时需要谨慎。这需要你适应这种编程方式。

模版中的模版

为构建一个复杂的数据结构,你可以将一个模板植入另一个模板中(即“模版嵌套”)。一般最好的方法是在程序前面使用typedef关键字来定义一个在另一个模板中使用的模版类型。

// 程序:在向量中嵌入向量的演示。
//目的:说明怎样使用嵌套的STL容器。

#include <iostream>
#include <vector>

using namespace std;

typedef vector <int> VEC_INT;

int inp[2][2] = {{1, 1}, {2, 0}}; 
  // 要放入模板中的2×2的正则数组

int main(int argc, char* argv[])
{
  int i, j;
  vector <VEC_INT> vecvec;
  // 如果你想用一句话实现这样的嵌套,你可以这样写:
  // vector <vector <int> > vecvec;
 
  // 将数组填入向量
  VEC_INT v0(inp[0], inp[0]+2); 
    // 传递两个指针
    // 将数组中的值拷贝到向量中
  VEC_INT v1(inp[1], inp[1]+2);

  vecvec.push_back(v0);
  vecvec.push_back(v1);

  for (i=0; i<2; i++)
  {
    for (j=0; j<2; j++)
    {
      cout << vecvec[i][j] << "  ";
    }
    cout << endl;
  }
  return 0;
}

// 输出:
// 1 1
// 2 0

虽然在初始化时很麻烦,一旦你将数据填如向量中,你就实现了一个变长的可扩充的二维数组(大小可扩充直到使用完内存)。根据实际需要,可以使用各种容器的嵌套组合。

总结

STL是有用的,但是使用过程中的困难和麻烦是再所难免的。就像中国人所说的:“如果你掌握了它,便犹如虎添翼。”

相关链接:
Josuttis Website :http://www.josuttis.com/
Pretty Good Initialization Library :http://www.codeproject.com/vcpp/stl/PGIL.asp