Xiao.Zhu C++

Xiao.Zhu C++

  C++博客 :: 首页 :: 新随笔 :: 联系 :: 聚合  :: 管理 ::
  29 随笔 :: 14 文章 :: 17 评论 :: 0 Trackbacks

一般全局变量应该用比较长,详细的名称,而本地变量则简单明了为宜。
GetSystemMetrics可以获得很多系统信息。

#i nclude <>先搜索/I编译选项的路径,然后搜索环境变量INCLUDE,""先搜索父文件的路径,然后才是前面两个路径。

用C设计一个动态增长的数组,需要用到MALLOC和REALLOC,对与同一个指针,如果内存不够存储数据,就用REALLOC重新分配多一倍的内存。并且返回的指针不能直接给原来的指针,因为如果分配失败,那么原来的数据就会丢失。

传递函数地址作为参数

首先定义以函数地址作为参数的函数

                  
fun1(返回类型(*fun2)(参数))
{
   ...=(*fun2)(参数);

}
fun2正常定义
调用的时候  fun1(函数名);
int fun2(int n)
{
 return n+1;

}

int fun1(int n,int(*f)(int b))
{
 return (*f)(n);

 

}
int main(int argc, char* argv[])
{
 int a(B),b;
    b=fun1(a,fun2);
    cout<<b;
 return 0;
}

实现散列表:使用一个散列函数,将项散列到一个数组里面,每个数组元素是一个链表,记录这个散列值的所有项。

CMemoryState 类可用于检查内存泄露。

 

 

构造函数和析构函数都没有返回值

类的成员缺省是私有的.
如果定义了构造函数和析构函数,必须将它们设置为PUBLIC,否则无法访问.
(事实上也可以是private,但只能访问类的静态成员,这时不能生成对象,如果要生成对象,需要一定的技巧)

重载依靠参数不同而不是返回值的不同,因为我们可以在调用函数的时候忽略返回值,这个时候编译器无法确定该调用那个函数.

构造函数可以根据参数类型不同区分,也可以根据参数个数不同区分,但如果一个构造函数的参数有缺省值而且前面的参数与其它构造函数相同,那么如果在调用的时候用到了缺省值,编译器无法分辨该调用那个函数,会报错.

可以在定义函数的时候,让某个参数只有类型名而没有标识符,这样做的目的是为了将来可能需要插入一个参数,在调用的时候随便给这个位置一个值就可以了.

int temp(int a,int,int b)
{}

~按位求反

const 存放在符号表里,不会被分配内存.如果使用&符号,就会强迫编译器为常量分配地址,const数组也会被分配内存.

const int* p;int const* p;指向常量的指针,它的内容(*p)不能被改变,也不能将它的值赋int * const p,常指针,值不变。给非常量指针。
对临时对象要使用常值引用接收,因为临时对象是常量。

对于const成员变量,必须在构造函数前(构造函数初始化表)对它赋初值(莄onst也可以在这里赋值,但没有必要,可以转到构造函数体内,虽然在初始化表里效率更高)。任何类型的成员变量都不能在声明的时候赋值。

类中的常量const是对某个具体对象而言的,如果需要对整个类的常值,使用enum.

char *p="123456";
cout<<*p++<<*p++;
*p=3;
char *t;
t=p;
输出结果为:21,因为求值顺序是从右到左,第三个语句是错的,*p的内存是不可写的,似乎p应该为一个常量指针,但确实可以将p赋给一个非常指针,语句5不报错.

如果成员函数被声明为const,那么其中不能含有改变成员变量值的语句(但可以改变被mutable修饰的成员变量)),也不能调用非const成员函数,如果对象被声明为const,那么它只能调用const成员函数.

volatile的用法同const,甚至可以使用const volatile做修饰没,volatile标识数据可能被别的进程改变,因此有必要在每次使用的时候重读这个数据,这在优化期间特别重要,防止编译器做一些假设.

给宏的参数最好是简单变量,如果不是,如a++,那么变量在宏中出现几次,a就会被加多少次。最好不用宏做类似函数的事情,在类中,用内联函数代替宏,一样可以得到高效率。

在类中定义的函数自动成为内联函数,在类外,使用inline关键字。

不应该使用public成员变量,而是应该使用内联函数存取这些变量,使用重载可以只用一个函数名字完成存取。

一个程序的所有文件,所有的名字(不在函数或类中)缺省都是外部连接,这意味着不同文件中相同的名字(不在函数或类中)会引起冲突,如果对这些名字用static修饰,就会变成内部连接,名字仅在编译单元内可见。extern是static的反义词,表示外部连接,同缺省的意义相同。两种连接的名字都会存储在静态存储区。

一旦用于修饰局部变量,static就不再表示可见性,而只改变变量的存贮类型。

register表示希望这个变量被放在寄存器里,因为它经常被用到,应该避免使用这个关键字,因为这方面通常机器比人更擅长。

类中的static变量(包括用static修饰的const)必须在类和函数外的全局位置初始化,初始化的语法是:static 类型 类::变量名=值;

存在嵌套类和局部类:类中定义的类和函数中定义的类,后者不能有静态成员变量(显然,没有办法初始化这种static变量).

调用c库,在声明的时候要使用extern "C" 函数声明,指明这是一个c连接.因为c++同c的编译器不同,会为函数产生不同的内部名,按照c++的方式连接c函数,会找不到库中的函数体.当然,通常情况下库的开发上已经为我们做好了这些.

引用必须被初始化,且引用的对象不能改变.不能引用null.

int f(const int&)
上面是一个常量引用,使用常量引用是为了保证外部变量不被修改,另外,如果传入的是常量或者临时对象,不使用常量引用的参数将出错.因为二者都是常量.

无返回值的函数是void类型.

void inf(int*&i){i++;}  调用:int *i=0; inf(i);
上面的函数是以指针引用做参数,改变指针的值.还可以使用指向指针的指针,要麻烦一些,不过表达更明确:void inf(int **i){(*i)++}; 调用时:int *i=0; inf(&i);

通过值传递给函数,或者函数返回一个对象,是使用位拷贝建立对象,这种情况下编译器会调用拷贝构造函数(如果没有编译器会建立一个缺省的),但对象销毁时会调用析构函数.

如果想禁止通过值传递某个对象,只要声明一个私有的拷贝构造函数,此时编译器认为用户接管了这项工作,不会建立缺省的拷贝构造函数,而用户建立的函数是私有的,没法调用,编译器就会报错.

可以定义指向类的成员变量和成员函数的指针,程序不必使用函数的名字就可以调用它,想起了高通的CDMA程序框架.c++编程思想第10章。

一个指向函数的指针:
void inf(int *&i){i++;}
int main(int argc, char* argv[])
{
int *i=0;
cout<<i<<endl;
void (*pf)(int *&);
pf=&inf;
(*pf)(i);
cout<<i<<endl;
}


运算符重载
重载仅是对用户类型的数据来说的,对内置的数据类型是不可以重载运算符的。
.和.*都不能重载.可以将运算符重载看作另外一种形式的函数调用,函数的名字是operator@,@代表运算符,参数的个数取决于两个因素:
1 运算符是一元还是二元
2 运算符是全局函数(一元是一个参数,二元是两个参数),还是成员函数(一元没有参数,二元一个参数----对象变为左侧参数)

可以重载几乎所有的运算符,但对于现在c中没有意义的运算符是不能重载的,也不能改变运算符的参数个数和优先级.

重载运算符的返回值:如果需要返回对象本身,根据需要返回
对象的指针或者引用,如果是返回临时生成的对象,那么返回对象.

重载运算符的返回值是否常量:当返回的是一个临时值得时候,如:%,&,>>,这些运算符得到的结果要赋给另外一个变量,这时返回值是const,如果返回值直接用于变量,如-=,+=,这是返回值不要加const.

函数返回对象的时候,返回一个临时对象比新建一个对象在返回效率要高很多,因为这时调用的是普通构造函数而不是拷贝构造函数,而且不需要调用析构函数,虽然新建一个对象再返回返回的也是一个临时对象.

智能指针(smart pointer):对象,包容器,迭代器.

自动类型转换:可以编程实现自动类型转换.如需要从对象one到two,那么只需要为two定义一个以one&为参数的构造函数,当编译器发现需要进行从对象one到two的转换的时候,会自动检查two的定义,找到这个构造函数,构造一个two对象.如果需要显式类型转换,在构造函数前加一个:explicit

还有一种自动类型转换方法是:为需要转换的对象重载一个运算符,运算符以要转换到的对象的名字命名.无须声明返回值.
operator one() const{ return one(x);}


不过并不提倡隐式类型转换,这样容易隐藏错误,也会降低调用时的效率.
 
使用全局重载运算符而不是成员运算符的好处是可以对左右操作书都自动作类型转换,而成员运算符的操作数左侧的必须是正确的对象

重载赋值操作符"=",返回可以是引用也可以是值,前者效率较高,但要记得此时返回的引用不能是属于局部对象的.通常返回*this.

return String(s1+s2); 与String temp(s1+s2);return temp;的效率是不同的,后者要进行对象拷贝,而前者直接将临时对象创建在函数的返回区。同时也更加简洁。

函数中少用static变量。让相同的输入产生相同的输出,这样的代码便于使用和维护。

对函数的参数和返回值的有效性进行检查。

积极使用断言(ASSERT),同时要加上注释,防止将来忘记ASSERT的目的。

之所以有了指针还要引入引用,是为了对功能加以限制,防止发生意外,就像对参数加上const限定的目的一样。

动态分配内存的原则:
1 分配后要检查是否分配成功,即if(p==NULL)
2 释放内存后要记得令p=NULL,防止产生野指针.野指针会让我们在使用指针前的if(p==NULL)检查形同虚设.

要申请一块内存复制数组char a[]的内容,应该申请的内存大小是sizeof(char)*(strlen(a)+1);

如果通过参数传递数组,数组名自动退化为一个指针.
main()
{
 char a[100];
        cout<<sizeof(a);
 fun(a);
}
void fun(char a[100])
{
   cout<<sizeof(a);
}
输出100 4.

对内存分配失败进行处理有两种方法:
1 if(p==NULL) 适用于内存分配语句较少的情况
2 _set_new_handler  _set_new_mode 适用于内存分配语句较多的情况

unsigned与没有unsigned 类型只是表示范围不同,大小相同.

 

如果不给类定义拷贝构造函数和赋值函数,如果类中有指针变量,就会导致错误,如果指针指向动态内存区,那这块内存会丢失,而两个指针相同一个块内存,导致其值无法判定,而且两个函数的析构函数会将这块内存释放两次,导致出错。
String a("hello");
String b("world");
String c(a); //调用拷贝构造函数,还可以写成:String c=a;但风格较差。
c=a;         //调用赋值函数(operator =) 赋值函数中注意先检查自赋值。

在继承当中,构造函数,析构函数,赋值函数都不能被继承,在编写子类时要注以下几点:
1子类必须在构造函数的初始化表调用基类的构造函数。
2父类和子类的析构函数都必须是virtual.//用于多态。
3子类赋值函数要调用父类的赋值函数:Base::operater=(other);


对函数参数和返回值进行const限定仅对指针和引用有意义,对值传递没有意义,对输出参数一定不要用const,不然无法输出参数。

重载new和delete的原因有两个:需要反复分配内存,需要亲自做这个工作提高效率,还有就是减少内存碎片,比如可以首先使用静态成员指针保留很大一块内存(在静态存储区),在其中完成内存的分配,并自己标记分配和释放,释放的时候,只是标记内存,而不free释放。
重载的new和delete只完成内存的分配和回收工作。new接受size_t函数,完成内存的分配,返回一个void*指针,delete接受一个void*指针,将它释放。
注意重载new和delete有两种不同的形式,一个用于每次创建一个对象,另一个用来创建一个对象数组,需要加上[]。如果重载了前者,那么在创建对象数组的时候,系统会调用全局的new和delete.

发现一个有趣的现象,可以使用值为NULL的指针调用任意对象的成员函数,只要先强制转换到这个对象,并且调用的是纯代码。

成员对象的初始化可以和父类构造函数的调用并排放在初始化表。

在进入构造函数的左括号前,所有的成员变量都必须被初始化。

构造函数,析构函数,赋值运算赋不被继承。

类的友元能够访问其private,protected成员,子类能访问类的protected成员。

不要在析构函数中抛出异常,因为异常处理函数在获得异常后要调用析构函数清理对象,此时再发生异常会导致程序无法再捕获异常,只能终止(只能在自定义的set_terminate()中作最后的处理。)。

拷贝字符串的方法
char dest[sz];
memset(dest,0,sz);
strncpy(dest,source,sz-1);
这样保证了不会超过缓冲区且结尾为'\0'.

异常处理函数会首先调用所有在try块中创建了的对象的析构函数,然后执行异常处理函数,然后继续运行后面的程序。但问题是,如果一个析构函数出现了异常,在析构函数中异常前创建的堆上的所有对象都无法调用其析构函数正常销毁。方法是使用模板,并自初始化表创建这些模板对象。

set_unexpceted可以截获没有被函数异常规格说明包括得异常,还可以简单的用一个throw;将这个异常作为已知异常再次抛出,如果有相应的catch语句,那么就可以捕获这个异常。

抛出异常的子类,会被能够捕获其父类异常的处理器捕获。这时会产生切片,即处理器收到的是一个父类,使用引用而不是传递值可以避免这个问题。
      try
  {
   throw(except("got it"));
  }
  catch(except &t)
  {
   t.what();
  }


运行时类形识别对void指针无效。

dynamic_cast<>用于向下映射。
base* b=new derived;
derived* d=dynamic_cast<derived*>b; 如果dynamic_cast失败的话,将返回NULL,可以以此来试探着判断指针b的类型。

RTTI还可以使用typeinfo().name()的方法返回对象id。typeinfo()返回typeinfo对象,使用前要包含头文件typeinfo.h.

class B
class D:public B

B* p=new D;
B& r=*p;

typeid(p)==typeid(B*)
typeid(r)!==typeid(D)
typeid(*p)==typeid(D)
typeid(&r)==typeid(B*)

对引用的动态映射也要制定到一个引用上,如果失败不是返回NULL,因为应用不许为空,而是产生一个异常,因此对引用的动态映射必须使用异常处理。

对空指针使用typeid()也会产生异常,可以在使用之前检查指针是否为NULL来避免这个问题。

在对重继承的情况下,传统的强制类型转换可能无法正常工作,但动态映射和typeid工作的很好。

要是动态类型类型转换,需要基类包含virtual成员函数,并且vc编译器有/GR选项。经过动态类型转换,由父类转换而来的子类指针可以调用子类中新添加而父类中没有的方法。

static_cast 通常不是必需的,但它会让类型转换更加醒目。

const_cast用于将常量和volatile映射给普通指针。

reinterpret_cast是危险并且可移植性很差的转换,它将对象看作二进制数进行转换。最好不要使用。

C++中,将结构名直接作为类型名使用,而不需要象c中那样使用typedef  struct 结构名{} 类型名;

                    WINDOWS核心编程
内核对象:每个内核对象都是一个内存块,由内核维护,进程在创建了一个内核对象后获得一个句柄,通常一个进程的句柄对另外一个进程是没有意义的,但可以通过一定措施在进程间共享内核对象。当进程终止后,它创建的内核对象不一定消失,内核维护每个内核对象的引用计数。


GDI对象不是内核对象,区分内核对象和GDI对象的方法是内核对象的创建函数的参数中有安全属性,而GDI对象没有。

内核对象的安全属性通常在创建服务器程序的时候用到,传递一个NULL可以获得缺省的安全属性。

当不再使用某个内核对象的时候,可以使用BOOL CloseHandle(HANDLE)关闭句柄,系统会自动将内核对象信息清除出进程的句柄表(此句柄表保存且仅保存该进程使用的所有内核对象信息。),并自动为内核对象的引用计数减一。如果忘记关闭句柄也不要紧,在进程推出后,系统会自动检查进程的句柄表,清理没有释放的句柄,因此忘记关闭句不一定会造成内存泄漏。

程序的进入点WinMain的第一个参数时进程的实例句柄,也是进程映射到虚拟地址空间的起始地址,vc++默认是0x00400000.可以用GetModuleHandle()得到这个值
PTSTR GetCommandLine()获得命令行
PWSTR CommandLineToArgvW()分解命令行

每个进程都有一个与他相关的环境块。
VarName1=VarValue1\0
VarName2=VarValue2\0
...............
\0

GetEnvironmentVariable()  //获得环境变量值
ExpandEnvironmentStrings()//展开%包裹的环境变量值
SetEnvironmentVariable()  //设定环境变量

进程的亲缘性:进程的线程被强迫再CPU的子集上运行。

子进程默认继承父进程的错误标志。
SetErrorMode(UINT)  //设定错误模式

进程维护当前驱动器和目录信息
GetCurrentDirectory()
SetCurrentDirectory()

获得系统版本:
GetVersion()
GetVersionEx()
VeryfyVersionInfo()


GetExitCodeProcess(),对于还在运行的进程,可以得到0x103(STILL_ACTIVE),对于终止的进程,如果还没有CloseHandle(pi.hProcess),可以得到它的退出码,否则得到的是乱码。

windows2000支持作业管理, 通过将进程加入作业,可以对进程的运行权限,使用的资源进行限制。方法如下:
HANDLE hjob=CreateJobObject(NULL,NULL);//创建一个作业对象。
SetInformationJobObject();//设定作业对象的参数,包括对进程的各种限制。
CreateProcess(NULL,"CMD",NULL,NULL,FALSE,CREATE_SUSPENDED,NULL,NULL,&si,&pi);//创建新进程。
AssignProcessToJobObject(hjob,pi.hProcess); //将进程加入作业。可加入多个。
ResumeThread(pi.hThread);
CloseHandle(pi.hThread);
HANDLE h[2];
h[0]=pi.hProcess;
h[1]=hjob;
DWORD dw=WaitForMultipleObject(2,h,false,INFINITE);
switch(dw-WAIT_OBJECT_0)
  case 0://the process has terminated..
  case 1://all of the job's allotted cpu time was used.
}
CloseHandle(pi.hProcess);
CloseHandle(hjob);

终止作业中所有进程的运行
TerminateJobObject(hjob,UINT uExitCode)

查询作业统计信息
QueryInformationJobObject();

监视作业的运行:
JOBOBJECT_ASSOCIATE_COMPLETION_PORT joacp;//创建一个I/O完成端口对象
SetInformationJobObject(hjob.JobObjectAssociateCompletionPortInformation,&joacp,sizeof(joacp)
//将作业同完成端口对相关联。
GetQueuedCompletionStatus()  //监控I/O端口。

进程由两部分组成:进程内核对象, 地址空间。进程是不活泼的,它的执行依赖于线程。
线程由两部分组成:线程内核对象,线程堆栈。


创建新线程:
DWORD WINAPI FUNC(PVOID pvParam)
int Param;
DWORD dwThreadID;
CreateThread(NULL,0,FUNC,(PVOID)&Param,0,&dwThreadID);

检查线程是否退出:
BOOL GetExitCodeThread(HANDLE hThread,PDWORD pdwExitCode);//如果还未终止,得到0x103.


获得伪句柄:
GetCurrentProcess()
GetCurrentThread()

获得运行时间:
GetProcessTimes()
GetThreadTimes()

线程或进程的伪句柄转化为实句柄:
DuplicatgeHandle();//此函数会增加内核对象的引用计数。

伪句柄用于本线程,获得这个句并不会影响内核对象的计数,而实句柄用于传递给子进程。

线程的暂停和运行:
ResumeThread(HANDLE)
SuspendThread(HANDLE)  //使用此函数要小心死锁。

线程休眠:
Sleep(DWORD dwMilliseconds);
自动退出当前时间片:
SwitchtoThread();

可以获得和修改线程的上下文,使用之前要SuspendThread()
GetThreadContext()
SetThreadContext()

改变进程的优先级://记住进程是不可以调度的,调度的单位是线程。
BOOL SetPriorityClass();
DWORD GetPriorityClass();

设定线程的相对优先级:
int GetThreadPriority(HANDLE hThread);
BOOL SetThreadPriority(Handle hThread,int nPriority);

Microsoft保留了随时修改调度算法的权利,因此使用相对优先级,可以保证程序在将来的系统上也可以正常运行。
结合进程优先级和线程的相对优先级,就可以得到线程的基本优先级。线程的当前优先级不可以低于基本优先级,
也就是说,系统会提高线程的优先级,并随着执行时间片的流逝降低优先级,但降到基本优先级后就不再降了。
优先级0-15成为动态优先级范围,高于15是实时范围,系统不会调度实时范围线程的优先级,也不会把动态优先级范围的
线程提高到15以上。

亲缘性是对多处理器系统来说的,为了能利用保留在cpu高速缓存和NUMA(非统一内存访问)结构计算机本插件板上内存中的数据,系统尽量线程上次运行使用的CPU来运行线程,包括软亲缘性(WIN2000默认)和硬亲缘性(用户可以选择CPU)
相关的函数有:
BOOL SetProcessAffinityMask(HANDLE hProcess,DWORD_PTR dwProcessAffinityMask);
BOOL GetProcessAffinityMask(Handle hProcess,PDWORD_PTR pdwProcessAffinityMask,PDWORD_PTR pdwSystemAffinityMask);
DWORD_PTR SetThreadAffinityMask(HANDLE hThread,DWORD_PTR dwThreadAffinityMask);
DWORD_PTR SetThreadIdealProcessor(HANDLE hThread,DWORD dwIdealProcessor);

临界区保证其中的资源(通常是各种共享变量)被原子的访问,当进入临界区后,其他访问这些资源的线程将不会被调度。

线程同步包括用户方式和内核方式,用户方式包括原子操作和临界区,它的特点是速度快,但功能有限。内核方式利用内核对象的通知状态来同步线程,由于需要由用户方式切换到内核方式(这种切换很废时间),且系统要进行很多操作,效率较低,但功能强大(能够设定超时值等,可以同步多个进程的线程)。

内核方式同步的原理:线程使自己进入休眠状态,等待内核对象由未通知状态变为已通知状态。

可处于未通知状态变和已通知状态的内核对象:进程,线程,作业,文件修改通知,时间,可等待定时器,文件,控制台输入,信号量,互斥体。

进程和线程在建立时处于未通知状态,在退出时变为已通知状态。

等待函数:
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
DWORD WaitForMultipleObject(DWORD dwCount,CONST HANDLE* phObjects,BOOL fWaitALL,DWORD dwMilliseconds);其中,
0<dwCount<WAIT_OBJECTS(windows头文件中定义为64),如果设定fWaitALl为TRUE,那么函数会知道左右对象变为已通知状态才会返回,如果传递FALSE,那么只要有一个对象变为已通知状态,函数就会返回。
返回值的含义:
HANDLE h[3];
h[0]=hProcess1;
h[1]=hProcess2;
h[2]=hProcess3;
DWORD dw=WaitForMultipleObject(3,h,FALSE,5000);
switch(dw)
{
    case WAIT_FAILED://Bad call to function(invalid handle?)
         break;
    case WAIT_TIMEOUT://None of the object became signaled within 5000 milliseconds.
         break;
    case WAIT_OBJECT_0+0:The process identified by h[0] terminated.
         break;
    case WAIT_OBJECT_0+1:
         break;
    case WAIT_OBJECT_0+2:
         break;
}
//WaitForSingleObject()的返回值只有前三种情况。如果给WaitForMutipleObject()的fWaitAll参数传递TRUE,那么其返回值也只有前三种。

事件内核对象:有两种,人工事件对象:当它得到通知的时候,所有等待的线程都变为可调度线程;自动重置的事件:当事件得到通知的时候,只有一个等待线程变为可调度的线程。创建事件内核对象:
HANDLE CreateEvent(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,BOOL fInitialState,PCTSTR pszName);
将事件改为通知状态:
BOOL SetEvent(HANDLE hEvent);
将事件改为未通知状态:
BOOL ResetEvent(HANDLE hEvent);
如果事件是自动重置事件,那么成功等待会产生副作用,即将事件自动置为未通知状态。如果是人工事件对象,则没有副作用。

等待定时器内核对象:是在某个时间或按规定的间隔时间发出自己的信号通知的内核对象。
HANDLE CreateWaitableTimer(PSECURITY_ATTRIBUTES psa,BOOL fManualReset,PCSTR pszName);
初始总是未通知状态。
BOOL SetWaitableTimer(
HANDLE hTimer,
const LARGE_INTEGER *pDueTime,
LONG lPeriod,
PTIMERAPCROUTINE pfnCompletionRoutine,
PVOID pvArgToCompletionRoutine,
BOOL fResume);
取消定时器:
BOOL CancelWaitableTimer(HANDLE hTimer);
如果仅想改变报时条件,不用调用这个函数暂停报时器,直接调用SetWaitableTimer()就可以了。

信号量内核对象
如果当前资源的数量大于0,发出信号
如果当前资源数量等于0,不发出信号
决不允许资源数量为负值。
创建信号量:
HANDLE CreateSemaphore(PSECURITY_ATTRIBUTE psa,
LONG lInitialCount,
LONG lMaximumCount,
PCSTR pszName);
递增资源:
BOOL ReleaseSemaphore(HANDLE hsem,
LONG lReleaseCount,
PLONG plPreviousCount);

互斥体内核对象:互斥体确保对单个资源的互斥访问。它包含一个使用数量,一个线程ID,一个递归计数器
与临界区的区别:能够同步多个进程中的线程,可以设定超时值。
如果ID为0,那么表示没有线程占用互斥体,互斥体发出信号。
如果ID不为0,表示占用资源的线程ID,不发出信号。

HANDLE CreateMutex(PSECURITY_ATTRIBUTES psa,
BOOL fInitialOwner,
PCTSTR pszName);

释放资源:
BOOL ReleaseMutex(HANDLE hMutex);

额外的函数:
DWORD SingalObjectAndWait(
HANDLE hObjectToSignal,
HANDLE hObjectToWaitOn,
DWORD dwMilliseconds,
BOOL fAlertable);
发出一个通知信号并等待另一个通知,效率比分别操作提高很多。

windows2000提供了如下几种线程池函数用于线程管理:
一、异步调用函数:
BOOL QueueUserWorkItem(
PTHREAD_START_ROUTINE pfnCallback,
PVOID pvContext,
ULONG dwFlags);
该函数将“工作项目”放入线程池并且立即返回。工作项目是指一个用pfnCallback参数标识的函数。它被调用并且传递单个参数pvContext.工作项目函数原型如下:
DWORD WINAPI WorkItemFunc(PVOID pvContext);
dwFlags参数:WT_EXECUTEDEFAULT  工作项目放入非I/O组件得线程中
             WT_EXECUTEINIOTHREAD 工作项目放入I/O组件的线程中,这样的线程在I/O请求没有完成之前不会被终止运行                                  ,防止因为线程被终止导致I/O请求丢失。
             WT_EXECUTEINPERSISTENTTHREAD 放入永久线程池,
             WT_EXECUTELONGFUNCTION  工作项目需要长时间的工作,系统会据此安排更多的线程。

线程池不能设置线程个数的上限,否则排队个数超过线程个数上限的时候,会导致所有的线程都被中断。

工作项目函数如果访问了已经被卸载的DLL,会产生违规访问。


二、按规定的时间间隔调用函数
创建定时器队列:
HANDLE CreateTimerQueue();
在队列中创建定时器:
BOOL CreateTimerQueueTimer(
PHANDLE phNewTimer,
HANDLE hTimerQueue,
WAITORTIMERCALLBACK pfnCallback,
PVOID pvContext,
DWORD dwDueTime,
DWORD dwPeriod,
ULONG dwFlags);
工作回调函数原型如下:
VOID WINAPI WaitOrTimerCallback(
PVOID pvContext,
BOOL fTimerOrWaitFired);
dwFlags比前面的多了一个标志:WT_EXECUTEINTIMERTHREAD,表示由组件的定时器线程(定时器组件只有一个线程)运行这个
工作函数,此时的工作函数必须是很快返回的,否则定时器组件将无法处理其他的请求。

删除定时器:
BOOL DeleteTimerQueueTimer(
HANDLE hTimerQueue,
HANDLE hTimer,
HANDLE hCompletionEvent);
在定时器线程中删除定时器会造成死锁。设定hCompletionEvent为INVALID_HANDLE_VALUE,那么在定时器的所有排队工作项目没有完成之前,DeleteTimerQueueTimer不会返回,也就是说在工作项目中对定时器进行中断删除会死锁。可以给hCompletionEvent传递事件句柄,函数会立即返回,在排队工作完成之后,会设置该事件。

重新设定定时器://不能修改已经触发的单步定时器。
BOOL ChangeTimerQueueTimer(
HANDLE hTimerQueue,
HANDLE hTimer,
ULONG dwDueTime,
ULONG dwPeriod;

删除定时器队列:
BOOL DeleteTimerQueueEx(
HANDLE hTimerQueue,
HANDLE hCompletionEvent);

三、当单个内核对象变为已通知状态时调用函数
BOOL RegisterWaitForSIngleObject(
PHANDLE phNewWaitObject,
HANDLE hObject,
WAITORTIMERCALLBACK pfnCallback,
PVOID pvContext,
ULONG dwMilliseconds,
ULONG dwFlags);
pfnCallBack原型:
VOID WINAPI WaitOrTimerCallbadkFunc(
PVOID pvContext,
BOOLEAN fTimerorWaitFired);
如果等待超时,fTimerorWaitFired==TRUE,如果是已通知状态,则为FALSE.

dwFlags可以传递参数:WT_EXECUTEINWAITTHREAD,它让等待组件得线程之一运行工作项目函数。注意项同前。

如果等待的内核对象是自动重置的,那么会导致工作函数被反复调用,传递WT_EXECUTEONLYONCE会避免这种情况。

取消等待组件的注册状态:
BOOL UnregisterWaitEx(
HANDLE hWaitHandle,
HANDLE hCompletionEvent);

四、当异步I/O请求完成时调用函数
将设备和线程池的非I/O组件关联
BOOL BindIoCompletionCallback(
HANDLE hDevice,
POVERLAPPED_COMPLETION_ROUTINE pfnCallback,
ULONG dwFlags//始终为0);

工作函数原型:
VOID WINAPI OverlappedCompletionRoutine(
DWORD dwErrorCode,
DWORD dwNumberOfBytesTransferred,
                         ,
POVERLAPPED pOverlapped);

Windows的内存结构

从98,2000,到64位的windows,内存管理方式都是不同的,32位的win2000用户内存是从0x10000到0x7fffffff(64kB-2G),2000 Advanced server可以达到(64kB-3G),其中最高64kB也是禁止进入的。再往上则由系统使用。98则是从0x400000-0x7fffffff(4M-2G),2G-3G是系统用来存放32位共享数据的地方,如很多系统动态连接库。0-4M是为了兼容16位程序保留的。3G-4G由系统自身使用。98的内核区是不受保护的,2000受保护。
对虚拟地址空间的分配称作保留,使用虚拟内存分配函数(VirtualAlloc),释放使用VirtualFree(),目前,所有cpu平台的分配粒度都是64kB,页面大小则不同,x86是4kB,Alpha是8kB,系统在保留内存的时候规定要从分配粒度边界开始,并且是页面的整数倍,用户使用VirtualAlloc都遵守这个规定,但系统不是,它是从页面边界开始分配的。

将物理存储器映射到保留的内存区域的过程称为提交物理存储器,提交是以页面为单位进行的,也使用VirtualAlloc函数。

物理存储器是由内存和(硬盘上的)页文件组成的,如果访问的数据是在页文件中,则称为页面失效,cpu会把访问通知操作系统,操作系统负责将数据调入内存,并指导cpu再次运行上次失效的指令。

当启动一个程序的时候,系统并不是将整个文件读入内存或者页文件,而是将这个文件直接映射到虚拟内存空间,并将需要的数据读入内存,即将硬盘上的文件本身当作页文件(虽然不是)。当硬盘上的一个程序的文件映像(这是个exe文件或者dll文件)用作地址空间的物理存储器,它称为内存映射文件。当一个.exe或者dll文件被加载时,系统将自动保留一个地址空间的区域,并将该文件映射到该区域中。但系统也提供了一组函数,用于将数据文件映射到一个地址空间的区域中。


物理存储器的页面具有不同的保护属性:
PAGE_NOACESS
PAGE_READONLY
PAGE_READWRITE
PAGE_EXECUTE
PAGE_EXECUTE_READ
PAGE_EXECUTE_READWRITE
PAGE_WRITECOPY
PAGE_EXECUTE_WRITECOPY
后两个属性是配合共享页面机制使用的。WINDOWS支持多个进程共享单个内存块,比如运行notepad的10个实例,可以让他们共享应用程序的代码和数据,这样可以大大提高性能,但要求该内存块是不可写的。于是系统在调入.exe或者dll的时候,会计算那些页面是可以写入的,为这些页面分配虚拟内存。然后同其他的页面一起映射到一块虚拟内存,但赋PAGE_WRITECOPY或者PAGE_EXECUTE_WRITECOPY属性(通常包含代码的块是PAGE_EXECUTE_READ,包含数据的块是PAGE_READWRITE)。当一个进程试图将数据写入共享内存块时,系统会进行如下操作:寻找预先分配的一个空闲页面,将试图修改的页面拷贝到这个空闲页面,赋予PAGE_READWRITE或者PAGE_EXECUTE_READWRITE属性,然后更新进程的页面表,使得用户可以对新的页面进行写入。

还有三个特殊的保护属性:PAGE_NOCACHE PAGE_WRITECOMBINE PAGE_GUARD,前两个用于驱动程序开发,最后一个可以让应用程序在页面被写入的时候获得一个异常。

块的意思是一组相邻的页面,它们具有相同的保护属性,并且受相同类型的物理存储器支持。
赋予虚拟内存页面保护属性的意义是为了提高效率,而且这个属性总会被物理存储器的保护属性取代。

如果数据在内存中没有对齐,那么cpu要多次访问才能得到数据,效率很低。

内存管理函数:

获得系统信息:
VOID GetSystemInfo(LPSYSTEM_INFO psinf);//可以得到页面大小,分配粒度,最大内存地址,最小内存地址。

获得内存状态:
VOID GlobalMemoryStatus(LPMEMORYSTATUS pmst);

获得内存地址的某些信息:
DWORD VirtualQuery(
LPVOID pvAddress,
PMEMORY_BASIC_INFORMATION pmbi,
DWORD dwLength);

DWORD VirtualQuery(
HANDLE hProcess,
LPVOID pvAddress,
PMEMORY_BASIC_INFORMATION pmbi,
DWORD dwLength);

内存映射文件的优点:
1 节省页面文件;
2 加快程序启动;
3 在多个进程间共享数据。

进程的启动过程:
系统首先将.exe文件映射到地址空间,缺省基地址是0x400000,然后查询.exe的输入表,将其使用的所有.dll也映射到地址空间(基地址在每个.dll文件中,如果不能满足,需要重定位),然后将执行.exe的启动代码。此时.exe文件还在硬盘上。每次代码跳到一个尚未加载到内存的指令地址,就会出现一个错误,系统会发现这个错误,并将代码加再到内存中。
如果再创建这个.exe文件的一个实例。那么直接将原来的地址空间中的内容映射到新的地址空间就可以了。这样多个实例就可以共享相同的代码和数据。如果某个实例要改变共享内容,系统就为要更改的页面申请一个新的页面,将内容拷贝一份,然后用新的页面代替地址空间中原来页面的映射就可以了。98同2000不同,它不待修改便立即为所有的实例分配新的页面。

使用内存映射文件:
1 创建或打开一个文件内核对象:
HANDLE CreateFile(
PCSTR pszFileName,
DWORD dwDesiredAccess,
DWORD dwShareMode,
PSECURITY_ATTRIBUTES psa,
DWORD dwCreationDisposition,
DWORD dwFlagsAndAttributes,
HANDLE hTemplateFile);
失败的返回值是INVALID_HANDLE_VALUE
2 创建一个文件映射内核对象:
HANDLE CreateFileMapping(
HANDLE hFile,
PSECURITY_ATTRIBUTES psa,
DWORD fdwProtect,
DWORD dwMaximumSizeHigh,
DWORD dwMaximumSizeLow,
PCTSTR pszName);
如果给函数的fdwProtect传递PAGE_READWRITE标志,那么磁盘上文件的大小会变为同映像文件相同大小。
失败的返回值是NULL。

3 将文件映射到进程的地址空间:
PVOID MapViewOfFile(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap);
windows2000会根据要求将部分文件映射到地址空间,而win98总是把全部内容映射到地址空间,并且仅能映射到2G-3G空间,此空间为共享空间,所有的进程如果映射相同的文件,那么都会映射到相同的地址,一个进程甚至不必映射就可以访问这个空间里其他进程的映射文件,win2000多个进程映射同一个文件返回的地址通常是不同的。
4 从进程的地址空间中撤销文件数句的映像
BOOL UnmapViewOfFile(PVOID pvBaseAddress);

将文件映像写入磁盘:
BOOL FlushViewOfFile(
PVOID pvAddress,
SIZE_T dwNumberOfBytesToFlush);

windows保证单个文件映射对象的多个视图具有相关性。但不保证但个文件的多个映射对象有相关性。

使用MapViewOfFileEx代替MapViewOfFile可以设定文件映射的基地址:
PVOID MapViewOfFileEx(
HANDLE hFileMappingObject,
DWORD dwDesiredAccess,
DWORD dwFileOffsetHigh,
DWORD dwFileOffsetLow,
SIZE_T dwNumberOfBytesToMap,
PVOID pvBaseAddress);

使用内存映射文件在进程间共享数据
共享机制:RPC ,COM,OLE,DDE,窗口消息(WM_COPYDATA),剪贴板,邮箱,管道,套接字。
在单机上,它们的底层实现方法都是内存映射文件。

可以在页文件中直接创建文件映射对象,方法是给CreateFileMapping函数的hFile参数传递INVALID_HANDLE_VALUE.注意,
CreateFile()函数运行失败也会返回这个参数,因此一定要检查CreateFile()的返回值。记住,文件函数运行失败的可能性太大了。

第三章:多个进程共享对象。

堆栈:优点:可以不考虑分配粒度和页面边界之类的问题,集中精力处理手头的任务,缺点是:分配和释放内存块的速度比其他机制慢,并且无法直接控制物理存储器的提交和回收。

进程的默认堆栈是1MB,可以使用/HEAP链接开关调整大小,DLL没有相关的堆栈。

堆栈的问题在于:很多windows函数要使用临时内存块,进程的多个线程要分配内存块,这些内存都是在默认堆栈上分配的,但规定时间内,每次只能由一个线程能够分配和释放默认堆栈的内存块,其他想要处理内存块的线程必须等待。这种方法对速度又影响。可以为进程的线程创建辅助堆栈,但windows函数只能使用默认堆栈。

获取进程默认堆栈句柄:
HANDLE GetProcessHeap();

创建辅助堆栈的理由
1 保护组件:
多个组件的数据混合交叉的存放在一块内存里,那么一个组件的错误操作很容易影响到另外一个组件。而要定位错误的来源将十分困难。
2 更有效的内存管理
通过在堆栈中分配同样大小的对象,可以更加有效的管理内存,减少内存碎片。
3 进行本地访问:
将同种数据集中到一定的内存块,可以在操作的时候访问较少的页面,这就减少了RAM和硬盘对换的可能.
4 减少线程同步的开销:
通过告诉系统只有一个线程使用堆栈(创建堆栈时使用HEAP_NO_SERIALIZE标志给fdwOptions),可以避免堆栈函数执行额外的用于保证堆栈安全性的代码,提高效率,但此时用户必须自己维护线程的安全性,系统不再对此负责。
5 迅速释放堆栈。
因为数据单一,因此释放的时候只要释放堆栈即可,不必显示的释放每个内存块。

创建辅助堆栈:
HANDLE HeapCreate(
DWORD fdwOptions,
SIZE_T dwInitialSize,
SIZE_T dwMaximumSize);

从堆栈中分配内存:
PVOID HeapAlloc(
HANDLE hHeap,
DWORD fdwFlags,
SIZE_T dwBytes);注意:当分配超过(1MB)内存块的时候,最好使用VirtualAlloc();

改变内存块的大小:
PVOID HeapReAlloc(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem,
SIZE_T dwBytes);

检索内存块的大小:
SIZE_T HeapSize(
HANDLE hHeap,
DWORD fdwFlags,
LPVOID pvMem);

释放内存块:
BOOL HeapFree(
HANDLE hHeap,
DWORD fdwFlags,
PVOID pvMem);

撤销堆栈:
BOOL HeapDestroy(HANDLE hHeap);

使用辅助堆栈的方法:重载对象的new操作符,在辅助堆上分配内存,并给对象添加一个静态变量用于保存堆句柄。

其它堆栈函数:
获取进程中所有堆栈得句柄:
DWORD GetProcessHeaps(DWORD dwNumHeaps,PHANDLE pHeaps);
验证堆栈完整性:
BOOL HeapValidate(
HANDLE hHeap,
DWORD fdwFlags,
LPCVOID pvMem);
合并地址中的空闲块
UINT HeapCompact(
HANDLE hHeap,
DWORD fdwFlags);

BOOL HeapLock(HANDLE hHeap);
BOOL HeapUnlock(HANDLE

遍历堆栈:
BOOL HeapWalk(
HANDLE hHeap,
PProcess_HEAP_ENTRY pHeapEntry);

各个dll也可以有自己的输入表。

如何编写DLL:
在DLL的头文件中,有如下代码:
#ifdef MYLIB
#else
    #define MYLIB extern "C" __declspec(dllimport)   
#endif
在每个输出变量和输出函数的声明前,用MYLIB修饰。
在DLL的实现文件中,有如下代码:
#i nclude "windows.h"
#define MYLIB extern "C" __declspec(dllexport)
#i nclude "Mylib.h"

其它的同编写普通C++程序完全相同。 "C" 表示按C方式链接和调用函数。C++编译器缺省按照__stdcall方式编译和调用,这种方式会改变函数的内部名字。此处如果把"C"都去掉也可以,但C程序将无法调用。另外,使用GetProcAddress函数时也会发生困难,因为
编译程序已经把函数名字改变了,无法用原来的名字得到函数地址。(核心编程说的不明白,没想到这本书错误这么多)
发行的时候,将头文件、.lib文件和DLL文件给用户就可以了。lib文件的作用是说明了头文件中函数所在的DLL文件,如果没有lib文件,编译器将在链接过程中提示错误:unresolved external symbol 函数名。
事实上,调用DLL有两种方式,第一种是比较常用,即包含DLL的头文件,并在链接的时候将动态链接库同exe文件像连接,建立输入表。这个时候需要.lib文件。第二种方法exe文件中没有输入表,程序使用LoadLibrary(Ex)和GetProcAddress()显式的加载DLL文件(卸载用FreeLibrary()),这个时候不需要.lib文件。
HINSTANCE LoadLibrary(PCTSTR pszDLLpathName);
HINSTANCE LoadLibraryEx(PCTSTR pszDLLpathName,NULL,0);

两次调用LoardLibrary并不会装载两次dll文件,只是将dll映射进进程的地址空间。系统会自动为每个进程维护一个dll的计数。FreeLiabray会使计数减一,如果计数为0,系统就会将dll从进程的地址空间卸载。


HINSTANCE GetModuleHandle(PCTSTR pszModuleName);//确定dll是否已经被映射进地址空间。
HINSTANCE hinstDll=GetModuleHandle("MyLib");
if(hinstDll==NULL)
{
    hinstDll=LoadLibrary("MyLib");
}

DWORD GetModuleFileName(
    HINSTANCE hinstModule,
    PTSTR pszPathName,
    DWORD cchPath
}
可以获得某个模块(.exe或者dll)的全路径名。

几个函数的用法:(注意GetProcAddress()函数的用法,如何定义和使用一个函数指针)
typedef int (*MYPROC)(int,int);

int main()
{
 HINSTANCE t;
 t=LoadLibraryEx(TEXT("tt.dll"),NULL,0);
 if(t)
 {
  cout<<TEXT("load success")<<endl;

 }
 HINSTANCE hinstDll=GetModuleHandle("tt.dll");
 if(hinstDll==NULL)
 {
  cout<<TEXT("first load failed")<<endl;
  hinstDll=LoadLibrary("MyLib");
 }
 size_t sz=100;
 PTCHAR str=new TCHAR[sz];
 GetModuleFileName(t,str,sz);
 cout<<str<<endl;
 delete str;
 MYPROC add=NULL;
 add=(MYPROC)GetProcAddress(t,"add");
    if(NULL!=add)
    {
  cout<<(*add)(1,2)<<endl;
 }
 FreeLibrary(t);

 return 0;
}


UNICODE
ANSI/UNICODE通用的定义方法(转换只需要在编译的时候使用_UNICODE和UNICODE):
TCHAR _TEXT("success") PTSTR PCTSTR _tcscpy(),_tcscat();
使用BYTE PBYTE定义字节,字节指针和数据缓冲。
传递给函数的缓存大小:sizeof(szBuffer)/sizeof(TCHAR)
给字符串分配内存:malloc(nCharacters*sizeof(TCHAR));
其它的字符串函数:
PTSTR CharLower(PTSTR pszString);
PTSTR CharUpper(PTSTR pszString);
转换单个字符:
TCHAR c=CharLower((PTSTR)szString[0]);
转换缓存中的字符串(不必以0结尾):
DWORD CharLowerBuff(
PTSTR pszString,
DWORD cchString);
DWORD CharUpperBuff(
PTSTR pszString,
DWORD cchString);

BOOL IsCharAlpha(TCHAR ch);
BOOL IsCharAlpahNumeric(TCHAR ch);
BOOL IsCharLower(TCHAR ch);
BOOL IsCharUpper(TCHAR ch);

线程本地存储(TLS):为进程的每个线程存储私有数据。用于那些一次传递参数后多次调用的函数(函数会保存上次调用的数据)。
实现方法:进程中有一个位标志树组(win2000的这个数组大小超过1000)。在每个线程中有一个对应的PVOID数组。通过设定位标志树组的某个位来分配每个线程中的PVOID数组得相应单元。函数需要每次检索线程的PVOID数组,获得该线程的相应数据。

DWORD TlsAlloc();  //为每个线程分配一个空的PVOID数组单元。

BOOL TlsSetValue(  //线程设定自己的PVOID数组单元。
DWORD dwTlsIndex,
PVOID pvTlsValue);

PVOID TlsGetValue(
DWORD dwTlsIndex);  //检索PVOID数组。

BOOL TLSFree(
DWORD dwTlsIndex);  //释放PVOID数组单元

静态TLS:__declspec(thread) DWORD gt_dwStartTime=0;//只能修饰全局或者静态变量。

DLL挂接(进程注入):让自己的DLL插入到其他进程的地址空间。

1 使用注册表插入DLL
HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs
将你的DLL路径放入这个关键字下面。当User32.dll被映射到进程中的时候,它会加在这个关键字下的每个库。
注意:(1)对win98无效
      (2)由于加载事间比较早,你的DLL可能无法调用kernel32以外的dll.
       (3) 如果进程没有使用user32.dll,这个方法无效。
       (4) 需要重新启动。
      (5)user32不会检查每个库是否加载成功。
2 使用windows钩子
HOOK hHook=SetWindowsHookEx(WH_GETMESSAGE,GetMsgProc,hinstDll,0);
BOOL UnhookWindowsHookEx(HHOOK hhook);
具体过程如下:
      (1)进程B的一个线程准备发送消息给一个窗口。
      (2)系统察看线程上是否已经安装了WH_GETMESSAGE钩子。
      (3)系统察看GetMsgProc的DLL是否已经映射到了进程B的地址空间,如果没有,系统将把DLL映射到B的地址空间,并自动增加引用计数。
      (4)调用GetMsgProc函数,返回时,系统会自动为DLL的引用计数减一。

3 使用远程线程来插入DLL
      (1)使用VirtualAllocEx,分配远程进程的地址空间的内存。
      (2)使用WriteProcessMemory,将Dll的路径名拷贝到第一个步骤中已经分配的内存中。
      (3)使用GetProcAddress,获得LoadLibrary的实际地址。
      (4)使用CreateRemoteThread,在远程进程中创建一个线程。
      退出: 
      (5)使用VirtualFreeEx,释放内存
      (6)使用GetProcAddress,获得FreeLiabary的地址。
      (7)使用CreateRemoteThread,在远程进程中创建一个线程,调用FreeLiabary函数。
4 使用特洛伊DLL插入
  替换dll.
5 将DLL作为调试程序插入
6 win98内存映射文件,creatprocess

结构化异常处理:
结束处理程序:__try{} __finally{}
除非__try执行中进程或者线程结束,否则总会执行__finally,并且__finally中的return会替代__try中的return;好的习惯是将return ,continue,break,goto语句拿到结构化异常处理语句外面,可以节省开销。将__try中的return 换成__leave,可以节省开销。在__finally总确定时正常进入还是展开进入:
BOOL AbnormalTermination();//正常进入返回FALSE,局部展开或者全局展开返回TRUE;

异常处理程序:__try{}__exception(异常过滤表达式){}

EXCEPTION_EXECUTE_HANDLE
表示处理异常,处理后转到exception块后面的代码继续执行。
EXCEPTION_CONTINUE_EXECUTION
EXCEPTION_CONTINUE_SEARCH

可以对异常过滤表达式进行硬编码,也可以用一个调用一个函数来决定过滤表达式,函数的返回值是LONG.例如,可以进行一定处理,然后返回EXCEPTION_CONTINUE_EXECUTION,再次执行出错语句。但可能再次出错,因此这种方法必须小心,防止生成死循环。

DWORD GetExceptionCode() 可以获得异常种类。它只能在__except后的括号或者异常处理程序中调用。

发生异常后,操作系统会像引起异常的线程的栈里压入三个结构:EXCEPTION_RECORD CONTEXT EXCEPTION_POINTERS,其中第三个结构包含两个成员指针,分别指向前两个结构,使用函数可以获得第三个结构的指针:
PEXCEPTION_POINTERS GetExceptionInformation();//仅可以在异常过滤器中调用,既__exception后面的小括号。

逗号表达式:从左到右对所有的表达式求值,并返回最有面的表达式的值。

引发软件异常:
VOID RaiseException(
DWORD dwExceptionCode,
DWORD dwExceptionFlags,
DWORD nNumberOfArguments,
CONST ULONG_PTR *pArguments);

缺省调试器所在注册表:
HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows NT\CurrentVersion\AeDebug  Debugger
win98是存放在win.ini里

调试器挂接到被调试进程
BOOL DebugActiveProcess(DWORD dwProcessID);


while(*str++!='\0');
应该注意的是在不满足条件后,str仍然会自加1。

位操作符>>和<<不会做循环位移,即不会把移出的位放到另一头。

多态性和动态联编的实现过程分析

  一、基础:

  1、多态性:使用基础类的指针动态调用其派生类中函数的特性。

  2、动态联编:在运行阶段,才将函数的调用与对应的函数体进行连接的方式,又叫运行时联编或晚捆绑。

  二、过程描述:

  1、编译器发现一个类中有虚函数,编译器会立即为此类生成虚拟函数表 vtable(后面有对vtable的分析)。虚拟函数表的各表项为指向对应虚拟函数的指针。

  2、编译器在此类中隐含插入一个指针vptr(对vc编译器来说,它插在类的第一个位置上)。

  有一个办法可以让你感知这个隐含指针的存在,虽然你不能在类中直接看到它,但你可以比较一下含有虚拟函数时的类的尺寸和没有虚拟函数时的类的尺寸,你能够发现,这个指针确实存在。

  3、在调用此类的构造函数时,在类的构造函数中,编译器会隐含执行vptr与vtable的关联代码,将vptr指向对应的vtable。这就将类与此类的vtable联系了起来。

  4、在调用类的构造函数时,指向基础类的指针此时已经变成指向具体的类的this指针,这样依靠此this指针即可得到正确的vtable,从而实现了多态性。在此时才能真正与函数体进行连接,这就是动态联编。

定义纯虚函数方法:virtual returntype function()= 0;

所有的类型都可以用new动态创建,包括类,结构,内置数据类型。

exit()函数包含在"cstdlib"中

泛型编程,使用STL库
总共有近75个泛型算法

所有容器的共通操作:
== != = empty() size() clear(),begin(),end(),以及insert和erase,不过后者随容器的的不同而不同

序列式容器:
vector(数组):插入和删除的效率较低,但存取效率高。
list(双向链表):与前者相反,插入和删除的效率较高,但存取效率低。每个元素包含三个字段:value back front.
deque(队列):在前端和末尾操作效率高。

生成序列式容器的五种方法:
1 产生空的容器:
list<string> slist;
vector<int> vtor;
2 产生特定大小的容器,容器中的每个元素都以其默认值为初值(发现VC中的int double等没有默认值)。
list<int> ilist(1024);
vector<string> svec(24);
3 产生特定大小的容器,并为每个元素指定初值:
list<int ilist(1024,0);
vector<string> svec(24,"default");
4 通过一对迭代器产生容器,这对迭代器用来表示数组作为初值的区间:
int ia[10]={1,2,3,4,5,6,7,8,9,0};
vector<int>  iv(ia+2,ia+8);
5 复制某个现有的容器的值:
vector<int> ivec1;
//填充ivec1;
vector<int> ivec2(ivec1);

有6个方法用于操作开始和末尾的元素:push_front() pop_front() push_back() pop_back(),由于pop操作仅删除元素而不返回元素,因此还需要front() back()方法取开始和末尾的元素,另外,vector不包括push_front() pop_front方法,很显然,无法实现。

intert的四种变形:
iterator insert(iterator position,elemType value):将value插到position前。返回值指向被插入的元素。
void insert(iterator position,int count,elemType value):在position前插入count个元素,每个元素都是value.
void insert(iterator1 position,iterator2 first,iterator2 last):将first,last之间的元素插到position前.
iterator insert( iterator position):在position前插入元素,初值为所属类型的默认值。

erase的两种变形:
1  iterator erase(iterator posit):删除posit指向的元素。
list<string>::iterator it=find(slist.begin(),slist,end(),str);
slist.erase(it);
2  iterator erase(iterator first,iterator last):删除first,last间的元素。

list不支持iterator的偏移运算

对于常值容器,使用常迭代器:
const vector<string> cs_vec;
vector<string::const_iterator iter_cs_vec.begin();

iterator可以当作指针用,可以用*取内容,也可以用->调用对象的成员。

使用泛型算法
#i nclude <algorithm>

find():线性搜索无序集合
binary_search():二分搜索有序集合。
count():返回元素个数。
search():搜索序列,如果存在返回的iterator指向序列首部,否则指向容器末尾。
max_element(begin,end):返回区间内的最大值。
copy(begin,end,begin):元素复制。
sort(begin,end):排序。

function objects:#i nclude <functional>
算术运算:
plus<type> minus<type> negate<type> multiplies<type> divides<type> modules<type>
关系运算:
less<type> less equal<type> greater<type greater equal<type> equal_to<type> not_equal_to<type>
逻辑运算:
logical_and<type> logical_or<type> logical_not<type>

adapter:适配器。
bind1st:将数值绑定到function object的第一个参数。
bind2nd:将数值绑定到function object的第二个参数。

使用map:
#i nclude <map>
#i nclude <string>
map<string,int> words;

words["vermeer"]=1;

map<string,int>::iterator it=words.begin();
for(;it!=words.end();++it)
cout<<"key:"<<it->first<<"value:"<<it->second<<endl;

查找map元素的方法:
words.find("vermeer");//返回iterator,指向找到的元素,找不到返回end();
还可以:
if(words.count(search_word))
count=words[search_word];

使用set:
#i nclude <set>
#i nclude <string>
set<string> word_exclusion;
//判断是否存在某个元素
if(word_exclusion.count(tword))
//默认情况下,所有元素按less-than运算排列

//加入元素
iset.insert(ival);
iset.insert(vec.begin(),vec.end());

与set相关的算法
set_intersection() set_union() set_difference() set_symmetric_difference()

使用insertion adapters:
#i nclude <iterator>
back_inserter()
inserter()
front_inserter()

使用STL通常会有很多警告,为了避免在调试模式(debug mode)出现恼人的警告,使用下面的编译器命令:

#pragma warning(disable: 4786)

strncpy(dest,source,count) if(count〈=strlen(source)),那么null结尾不会被加在dest的尾部,如果count>strlen(source),那么不足的部分会用null填充。


windows内存是由高地址向底地址分配的,但变量的存储是从底地址到高地址的,如INT类型的四个字节,数组的每个元素。
 
内存复制的时候不能用字符串拷贝函数,因为即使使用strncpy指定了复制的长度,拷贝函数也会遇到'\0'自动终止,要使用MEMSET。

由于对齐的关系,下面两个结构使用sizeof,前者是12,后者是16。
struct DNSAnswer
{
 unsigned short name;
 unsigned short type;
 unsigned short cla;
 unsigned short length;
 unsigned int   ttl;
};
struct DNSAnswer
{
 unsigned short name;
 unsigned short type;
 unsigned short cla;
 unsigned int   ttl;
        unsigned short length;
};

子类可以使用父类的保护成员,而友元比子类的权限还大,可以使用类的私有和保护成员。

在内存分配失败的情况下,系统只有在出错处理函数为空的情况下,才会抛出异常:std::bad_alloc(),否则会反复调用处理函数并再次尝试分配内存。

如果重载了NEW,那么在继承的时候要小心,如果子类没有覆盖NEW,那么它会去使用父类的NEW ,因此应该在new,delete中做检查
if (size != sizeof(base))             // 如果数量“错误”,让标准operator new,base为类名
    return ::operator new(size);        // 去处理这个请求

  if (size != sizeof(base)) {      // 如果size"错误",
    ::operator delete(rawmemory);  // 让标准operator来处理请求
    return;                       
  }

c++标准规定,要支持0内存请求(分配一个字节),并且可以删除NULL指针(直接返回)。

在创建线程的时候,传递的变量一定要是全局或者静态的变量,因为传递的是变量的地址,如果是局部变量地址很快就会失效。

主线程退出后,其子线程自动结束。

智能指针:它可以避免内存泄露,因为智能指针是在栈上创建的;还可以避免堆上内存的重复释放错误,因为它保证只有一个指针拥有这块内存的所有权。

posted on 2007-08-13 09:22 Xiao.Zhu 阅读(1166) 评论(0)  编辑 收藏 引用

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理