Visual C++提供了对C语言、C++语言及MFC的支持,因而其涉及到的异常(exception)处理也包含了这三种类型,即C语言、C++语言和MFC的异常处理。除此之外,微软对C和C++的异常处理进行了扩展,提出了结构化异常处理(SEH)的概念,它支持C和C++(与之相比,MFC异常处理仅支持C++)。

一个典型的异常处理包含如下几个步骤:

(1)程序执行时发生错误;

(2)以一个异常对象(最简单的是一个整数)记录错误的原因及相关信息;

(3)程序检测到这个错误(读取异常对象);

(4)程序决定如何处理错误;

(5)进行错误处理,并在此后恢复/终止程序的执行。

C、C++、MFC及SEH在这几个步骤中表现出了不同的特点。本文将对这四种异常处理进行介绍,并对它们进行对比分析。本文例程的调试平台为Visual C++6.0,操作系统为Windows XP,所有程序均调试通过。

在进入正式的讲解之前,先说几句废话。许多的编程新手对异常处理视而不见,程序里很少考虑异常情况。一部分人甚至根本就不考虑,以为程序总是能以正确的途径运行。譬如我们有的程序设计者调用fopen打开一个文件后,立马就开始进行读写操作,根本就不考虑文件是否正常打开了。这种习惯一定要改掉,纵使你再不愿意!这是软件健壮性的需要!异常处理不是浪费时间!

1.C语言异常处理

1.1 异常终止

标准C库提供了abort()和exit()两个函数,它们可以强行终止程序的运行,其声明处于<stdlib.h>头文件中。这两个函数本身不能检测异常,但在C程序发生异常后经常使用这两个函数进行程序终止。下面的这个例子描述了exit()的行为:

#include <stdio.h>

#include <stdlib.h>

int main(void)

{

exit(EXIT_SUCCESS);

printf("程序不会执行到这里\n");

return 0;

}

在这个例子中,main函数一开始就执行了exit函数(此函数原型为void exit(int)),因此,程序不会输出"程序不会执行到这里"。程序中的exit(EXIT_SUCCESS)表示程序正常结束,与之对应的exit(EXIT_FAILURE)表示程序执行错误,只能强行终止。EXIT_SUCCESS、EXIT_FAILURE分别定义为0和1。

对于exit函数,我们可以利用atexit函数为exit事件"挂接"另外的函数,这种"挂接"有点类似Windows编程中的"钩子"(Hook)。譬如:

#include <stdio.h>

#include <stdlib.h>

static void atExitFunc(void)

{

printf("atexit挂接的函数\n");

}

int main(void)

{

atexit(atExitFunc);

exit(EXIT_SUCCESS);

printf("程序不会执行到这里\n");

return 0;

}

程序输出"atexit挂接的函数"后即终止。来看下面的程序,我们不调用exit函数,看看atexit挂接的函数会否执行:

#include <stdio.h>

#include <stdlib.h>

static void atExitFunc(void)

{

printf("atexit挂接的函数\n");

}

int main(void)

{

atexit(atExitFunc);

//exit(EXIT_SUCCESS);

printf("不调用exit函数\n");

return 0;

}

程序输出:

不调用exit函数

atexit挂接的函数

这说明,即便是我们不调用exit函数,当程序本身退出时,atexit挂接的函数仍然会被执行。

atexit可以被多次执行,并挂接多个函数,这些函数的执行顺序为后挂接的先执行,例如:

#include <stdio.h>

#include <stdlib.h>

static void atExitFunc1(void)

{

printf("atexit挂接的函数1\n");

}

static void atExitFunc2(void)

{

printf("atexit挂接的函数2\n");

}

static void atExitFunc3(void)

{

printf("atexit挂接的函数3\n");

}

int main(void)

{

atexit(atExitFunc1);

atexit(atExitFunc2);

atexit(atExitFunc3);

return 0;

}

输出的结果是:

atexit挂接的函数3

atexit挂接的函数2

atexit挂接的函数1

在Visual C++中,如果以abort函数(此函数不带参数,原型为void abort(void))终止程序,则会在debug模式运行时弹出如图1所示的对话框。

图1 以abort函数终止程序

1.2 断言(assert)

assert宏在C语言程序的调试中发挥着重要的作用,它用于检测不会发生的情况,表明一旦发生了这样的情况,程序就实际上执行错误了,例如strcpy函数:

char *strcpy(char *strDest, const char *strSrc)

{

char *address = strDest;

assert((strDest != NULL) && (strSrc != NULL));

while ((*strDest++ = *strSrc++) != ’\0’)

;

return address;

}

其中包含断言assert( (strDest != NULL) && (strSrc != NULL) ),它的意思是源和目的字符串的地址都不能为空,一旦为空,程序实际上就执行错误了,会引发一个abort。

assert宏的定义为:

#ifdef NDEBUG

#define assert(exp) ((void)0)

#else

#ifdef __cplusplus

extern "C"

{

#endif

_CRTIMP void __cdecl _assert(void *, void *, unsigned);

#ifdef __cplusplus

}

#endif

#define assert(exp) (void)( (exp) || (_assert(#exp, __FILE__, __LINE__), 0) )

#endif /* NDEBUG */

如果程序不在debug模式下,assert宏实际上什么都不做;而在debug模式下,实际上是对_assert()函数的调用,此函数将输出发生错误的文件名、代码行、条件表达式。例如下列程序:

#include <stdio.h>

#include <stdlib.h>

#include <assert.h>

char * myStrcpy( char *strDest, const char *strSrc )

{

char *address = strDest;

assert( (strDest != NULL) && (strSrc != NULL) );

while( (*strDest++ = *strSrc++) != ’\0’ );

return address;

}

int main(void)

{

myStrcpy(NULL,NULL);

return 0;

}

在此程序中,为了避免我们的strcpy与C库中的strcpy重名,将其改为myStrcpy。程序的输出如图2:

图2 assert的输出

失败的断言也会弹出如图1所示的对话框,这是因为_assert()函数中也调用了abort()函数。

一定要记住的是assert本质上是一个宏,而不是一个函数,因而不能把带有副作用的表达式放入assert的"参数"中。

1.3 errno

errno在C程序中是一个全局变量,这个变量由C运行时库函数设置,用户程序需要在程序发生异常时检测之。C运行库中主要在math.h和stdio.h头文件声明的函数中使用了errno,前者用于检测数学运算的合法性,后者用于检测I/O操作中(主要是文件)的错误,例如:

#include <errno.h>

#include <math.h>

#include <stdio.h>

int main(void)

{

errno = 0;

if (NULL == fopen("d:\\1.txt", "rb"))

{

printf("%d", errno);

}

else

{

printf("%d", errno);

}

return 0;

}

在此程序中,如果文件打开失败(fopen返回NULL),证明发生了异常。我们读取error可以获知错误的原因,如果D盘根目录下不存在"1.txt"文件,将输出2,表示文件不存在;在文件存在并正确打开的情况下,将执行到else语句,输出0,证明errno没有被设置。

Visual C++提供了两种版本的C运行时库。-个版本供单线程应用程序调用,另一个版本供多线程应用程序调用。多线程运行时库与单线程运行时库的一个重大差别就是对于类似errno的全局变量,每个线程单独设置了一个。因此,对于多线程的程序,我们应该使用多线程C运行时库,才能获得正确的error值。

另外,在使用errno之前,我们最好将其设置为0,即执行errno = 0的赋值语句。

1.4 其它

除了上述异常处理方式外,在C语言中还支持非局部跳转(使用setjmp和longjmp)、信号(使用signal、raise)、返回错误值或回传错误值给参数等方式进行一定能力的异常处理,但是其使用不如1.1~1.3节所介绍方式常用,我们不必过细研究。

从以上分析可知,C语言的异常处理是简单而不全面的。与C++的异常处理比起来,C语言异常处理相形见绌,它就像娘胎里的雏婴。

2.C++语言异常处理

2.1 C++异常处理语法

感谢C++语言的后期改造者们,他们在标准C++语言中专门集成了异常处理的相关语法(与之不同的是,所有的C 标准库异常体系都需要运行库的支持,它不是语言内核支持的)。当然,异常处理被加到程序设计语言中,也是程序语言发展和逐步完善的必然结果。我们看到,C++不是唯一集成异常处理的语言。

C++的异常处理结构为:

try

{

//可能引发异常的代码

}

catch(type_1 e)

{

// type_1类型异常处理

}

catch(type_2 e)

{

// type_2类型异常处理

}

catch (...)//会捕获所有未被捕获的异常,必须最后出现

{

}

而异常的抛出方式为使用throw(type e),try、catch和throw都是C++为处理异常而添加的关键字。看看这个例子:

#include <stdio.h>

//定义Point结构体(类)

typedef struct tagPoint

{

int x;

int y;

} Point;

//扔出int异常的函数

static void f(int n)

{

throw 1;

}

//扔出Point异常的函数

static void f(Point point)

{

Point p;

p.x = 0;

p.y = 0;

throw p;

}

int main()

{

Point point;

point.x = 0;

point.y = 0;

try

{

f(point); //抛出Point异常

//f(1); //抛出int异常

}

catch (int e)

{

printf("捕获到int异常:%d\n", e);

}

catch (Point e)

{

printf("捕获到Point异常:(%d,%d)\n", e.x, e.y);

}

return 0;

}

函数f定义了两个版本:f(int)和f(Point),分别抛出int和Point异常。当main函数的try{…}中调用f(point)时和f(1)时,分别输出:

捕获到Point异常:(0,0)

捕获到int异常:1

在C++中,throw抛出异常的特点有:

(1)可以抛出基本数据类型异常,如int和char等;

(2)可以抛出复杂数据类型异常,如结构体(在C++中结构体也是类)和类;

(3)C++的异常处理必须由调用者主动检查。一旦抛出异常,而程序不捕获的话,那么abort()函数就会被调用,弹出如图1所示的对话框,程序被终止;

(4)可以在函数头后加throw([type-ID-list])给出异常规格,声明其能抛出什么类型的异常。type-ID-list是一个可选项,其中包括了一个或多个类型的名字,它们之间以逗号分隔。如果函数没有异常规格指定,则可以抛出任意类型的异常。

2.2 标准异常

下面给出了C++提供的一些标准异常:

namespace std

{

//exception派生

class logic_error; //逻辑错误,在程序运行前可以检测出来

//logic_error派生

class domain_error; //违反了前置条件

class invalid_argument; //指出函数的一个无效参数

class length_error; //指出有一个超过类型size_t的最大可表现值长度的对象的企图

class out_of_range; //参数越界

class bad_cast; //在运行时类型识别中有一个无效的dynamic_cast表达式

class bad_typeid; //报告在表达试typeid(*p)中有一个空指针p

//exception派生

class runtime_error; //运行时错误,仅在程序运行中检测到

//runtime_error派生

class range_error; //违反后置条件

class overflow_error; //报告一个算术溢出

class bad_alloc; //存储分配错误

}

请注意观察上述类的层次结构,可以看出,标准异常都派生自一个公共的基类exception。基类包含必要的多态性函数提供异常描述,可以被重载。下面是exception类的原型:

class exception

{

public:

exception() throw();

exception(const exception& rhs) throw();

exception& operator=(const exception& rhs) throw();

virtual ~exception() throw();

virtual const char *what() const throw();

};

其中的一个重要函数为what(),它返回一个表示异常的字符串指针。下面我们从exception类派生一个自己的类:

#include <iostream>

#include <exception>

using namespace std;

class myexception:public exception

{

public:

myexception():exception("一个重载exception的例子")

{}

};

int main()

{

try

{

throw myexception();

}

catch (exception &r) //捕获异常

{

cout << "捕获到异常:" << r.what() << endl;

}

return 0;

}

程序运行,输出:

捕获到异常:一个重载exception的例子

一般的,我们直接以基类捕获异常,例如,本例中使用了

catch (exception &r)

然后根据基类的多态性进行处理,这是因为基类中的what函数是虚函数。

2.3异常处理函数

在标准C++中,还定义了数个异常处理的相关函数和类型(包含在头文件<exception>中):

namespace std

{

//EH类型

class bad_exception;

class exception;

typedef void (*terminate_handler)();

typedef void (*unexpected_handler)();

// 函数

terminate_handler set_terminate(terminate_handler) throw();

unexpected_handler set_unexpected(unexpected_handler) throw();

void terminate();

void unexpected();

bool uncaught_exception();

}

其中的terminate相关函数与未被捕获的异常有关,如果一种异常没有被指定catch模块,则将导致terminate()函数被调用,terminate()函数中会调用ahort()函数来终止程序。可以通过set_terminate(terminate_handler)函数为terminate()专门指定要调用的函数,例如:

#include <cstdio>

#include <exception>

using namespace std;

//定义Point结构体(类)

typedef struct tagPoint

{

int x;

int y;

} Point;

//扔出Point异常的函数

static void f()

{

Point p;

p.x = 0;

p.y = 0;

throw p;

}

//set_terminate将指定的函数

void terminateFunc()

{

printf("set_terminate指定的函数\n");

}

int main()

{

set_terminate(terminateFunc);

try

{

f(); //抛出Point异常

}

catch (int) //捕获int异常

{

printf("捕获到int异常");

}

//Point将不能被捕获到,引发terminateFunc函数被执行

return 0;

}

这个程序将在控制台上输出 "set_terminate指定的函数" 字符串,因为Point类型的异常没有被捕获到。当然,它也会弹出图1所示对话框(因为调用了abort()函数)。

上述给出的仅仅是一个set_terminate指定函数的例子。在实际工程中,往往使用set_terminate指定的函数进行一些清除性的工作,其后再调用exit(int)函数终止程序。这样,abort()函数就不会被调用了,也不会输出图1所示对话框。

关于标准C++的异常处理,还包含一些比较复杂的技巧和内容,我们可以查阅《more effective C++》的条款9~条款15。

3.MFC异常处理

MFC中异常处理的语法和语义构建在标准C++异常处理语法和语义的基础之上,其解决方案为:

MFC异常处理 = MFC 异常处理类 + 宏

3.1宏

MFC定义了TRY、CATCH(及AND_CATCH、END_CATCH)和THROW(及THROW_LAST)等用于异常处理的宏,其本质上也是标准C++的try、catch和throw的进一步强化,由这些宏的定义可知:

#ifndef _AFX_OLD_EXCEPTIONS

#define TRY { AFX_EXCEPTION_LINK _afxExceptionLink; try {

#define CATCH(class, e) } catch (class* e) \

{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); \

_afxExceptionLink.m_pException = e;

#define AND_CATCH(class, e) } catch (class* e) \

{ ASSERT(e->IsKindOf(RUNTIME_CLASS(class))); \

_afxExceptionLink.m_pException = e;

#define END_CATCH } }

#define THROW(e) throw e

#define THROW_LAST() (AfxThrowLastCleanup(), throw)

// Advanced macros for smaller code

#define CATCH_ALL(e) } catch (CException* e) \

{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \

_afxExceptionLink.m_pException = e;

#define AND_CATCH_ALL(e) } catch (CException* e) \

{ { ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \

_afxExceptionLink.m_pException = e;

#define END_CATCH_ALL } } }

#define END_TRY } catch (CException* e) \

{ ASSERT(e->IsKindOf(RUNTIME_CLASS(CException))); \

_afxExceptionLink.m_pException = e; } }

这些宏在使用语法上,有如下特点:

(1)用TRY 块包含可能产生异常的代码;

(2)用CATCH块检测并处理异常。要注意的是,CATCH块捕获到的不是异常对象,而是指向异常对象的指针。此外,MFC靠动态类型来辨别异常对象;

(3)可以在一个TRY 块上捆绑多个异常处理捕获块,第一次捕获使用宏CATCH,以后的使用AND_CATCH,而END_CATCH则用来结束异常捕获队列;

(4)在异常处理程序内部,可以用THROW_LAST 再次抛出最近一次捕获的异常。

3.2 MFC 异常处理类

MFC较好地将异常封装到CException类及其派生类中,自成体系,下表给出了MFC 提供的预定义异常:

异常类

含义

CMemoryException

内存不足

CFileException

文件异常

CArchiveException

存档/序列化异常

CNotSupportedException

响应对不支持服务的请求

CResourceException

Windows 资源分配异常

CDaoException

数据库异常(DAO 类)

CDBException

数据库异常(ODBC 类)

COleException

OLE 异常

COleDispatchException

调度(自动化)异常

CUserException

用消息框警告用户然后引发一般 CException 的异常

标准C++的异常处理可以处理任意类型的异常,而3.1节的MFC 宏则只能处理CException 的派生类型,下面我们看一个CFileException的使用例子:

#include <iostream.h>

#include "afxwin.h"

int main()

{

TRY

{

CFile f( "d:\\1.txt", CFile::modeWrite );

}

CATCH( CFileException, e )

{

if( e->m_cause == CFileException::fileNotFound )

cout << "ERROR: File not found\n" << endl;

}

END_CATCH

}

在这个程序中,如果D盘根目录下不存在"1.TXT"这个文件,将抛出CFileException异常,而且错误原因成员变量m_cause被设置为fileNotFound,我们以CATCH( CFileException, e )就可以捕获到。错误原因被定义为CFileException中的枚举(enum),如下:

enum {

none,

generic,

fileNotFound,

badPath,

tooManyOpenFiles,

accessDenied,

invalidFile,

removeCurrentDir,

directoryFull,

badSeek,

hardIO,

sharingViolation,

lockViolation,

diskFull,

endOfFile

};

我们在使用MFC相关类时,MFC会自动抛出异常,当然我们也可以自行在程序中利用AfxThrowXXXException()抛出各种类型的异常,其中的XXX与前文的MFC异常类表对应。我们看AfxThrowFileException的例子:

#include <iostream.h>

#include "afxwin.h"

int main()

{

TRY

{

AfxThrowFileException(CFileException::fileNotFound);

}

CATCH( CFileException, e )

{

if( e->m_cause == CFileException::fileNotFound )

cout << "ERROR: File not found\n" << endl;

}

END_CATCH

}

在此程序中,我们在TRY块自行利用MFC提供的全局函数AfxThrowFileException抛出了CFileException异常,其后在CATCH块抓住了这个异常。

MFC建议不再使用TRY、CATCH和THROW宏,而是直接使用标准C++的方式。

4.结构化异常处理

结构化异常处理(Structured Exception Handling,简称SEH)是微软针对Windows程序异常处理进行的扩展,在Visual C++中,它同时支持C和C++语言。SEH不宜与标准C++异常处理和MFC异常处理混用,对于C++程序,微软建议使用标准C++的异常处理。

为了支持SEH,Visual C++中定义了四个关键字(由于这些关键字是非标准关键字,其它编译器不一定支持),用以扩展C 和C++语言:

(1)__except

(2)__finally

(3)__leave

(4)__try

其基本语法为:

__try

{

...//可能导致异常的被监控代码块

}

__except(filter-expression)

{

...//异常处理函数

}

或:

__try

{

...

}

__finally

{

...//终止

}

其执行的步骤如下:

(1)__try块被执行;

(2)如果__try块没有出现异常,则执行到__except块之后;否则,执行到__except块,根据filter-expression的值决定异常处理方法:

a. filter-expression的值为EXCEPTION_CONTINUE_EXECUTION (-1)

恢复异常,从发生异常处下面开始执行,异常处理函数本身不被执行;

b. filter-expression的值为EXCEPTION_CONTINUE_SEARCH (0)

异常不被识别,拒绝捕获异常,继续搜索下一个异常处理函数;

c. filter-expression的值为EXCEPTION_EXECUTE_HANDLER (1)

异常被识别,终止异常,从异常发生处开始退栈,一路上遇到的终止函数都被执行。

看看这个例子:

//例4-1

#include "stdio.h"

void main()

{

int* p = NULL; // 定义一个空指针

puts("SEH begin");

__try

{

puts("in try");

__try

{

puts("in try");

*p = 0; // 引发一个内存访问异常

}

__finally

{

puts("in finally");

}

}

__except(puts("in filter"), 1)

{

puts("in except");

}

puts("SEH end");

}

程序的输出为:

SEH begin

in try //执行__try块

in try //执行嵌入的__try块

in filter //执行filter-expression,返回EXCEPTION_EXECUTE_HANDLER

in finally //展开嵌入的__finally

in except //执行对应的__except块

SEH end //处理完毕

如果我们把__except(puts("in filter"), 1)改为__except(puts("in filter"), 0),程序的输出将变为:

SEH begin

in try //执行__try块

in try //执行嵌入的__try块

in filter //执行filter-expression,返回EXCEPTION_CONTINUE_SEARCH

in finally //展开嵌入的__finally

程序的执行也告崩溃,弹出如图3所示的对话框。

图3 不能被正确执行的SEH

要想这个程序能正确地执行,我们可以在第一个__try块的外面再套一个__try块和一个接收filter-expression返回值为EXCEPTION_EXECUTE_HANDLER的__except块,程序改为:

//例4-2

#include "stdio.h"

void main()

{

int* p = NULL; // 定义一个空指针

puts("SEH begin");

__try

{

__try

{

puts("in try");

__try

{

puts("in try");

*p = 0; // 引发一个内存访问异常

}

__finally

{

puts("in finally");

}

}

__except(puts("in filter"), 0)

{

puts("in except");

}

}

__except(puts("in filter"), 1)

{

puts("in except");

}

puts("SEH end");

}

程序输出:

SEH begin

in try //执行__try块

in try //执行嵌入的__try块

in filter1 //执行filter-expression,返回EXCEPTION_CONTINUE_SEARCH

in filter2 //执行filter-expression,返回EXCEPTION_EXECUTE_HANDLER

in finally //展开嵌入的__finally

in except2 //执行对应的__except块

SEH end //处理完毕

由此可以看出,因为第一个__except的filter-expression返回EXCEPTION_CONTINUE_SEARCH 的原因,"in except1"没有被输出。程序之所以没有崩溃,是因为最终碰到了接收EXCEPTION_EXECUTE_HANDLER的第2个__except。

SEH使用复杂的地方在于较难控制异常处理的流动方向,弄不好程序就"挂"了。如果把例4-1中的__except(puts("in filter"), 1)改为__except(puts("in filter"), -1),程序会进入一个死循环,输出:

SEH begin

in try //执行__try块

in try //执行嵌入的__try块

in filter //执行filter-expression,返回EXCEPTION_CONTINUE_EXECUTION

in filter

in filter

in filter

in filter

…//疯狂输出"in filter"

最后疯狂地输出"in filter",我们把断点设置在__except(puts("in filter"), -1)语句之前,按F5会不断进入此断点。

5.各种异常处理的对比

下表给出了从各个方面对这本文所给出的Visual C++所支持的四种异常处理进行的对比:

异常处理

支持语言

是否标准

复杂度

推荐使用

C异常处理

C语言

标准C

简单

推荐

C++异常处理

C++语言

标准C++

较简单

推荐

MFC异常处理

C++语言

仅针对MFC程序

较简单

不推荐

SEH异常处理

C和C++语言

仅针对Microsoft编译环境

较复杂

不推荐

本文所讲解的仅仅是Visual C++异常处理的初步知识,对于更深入的内容,还需要我们在不断的编程过程中去领悟和学习。

在程序设计过程中,我们不能嫌异常处理"麻烦",对可能的错误视而不见、不加考虑。因为避免了异常处理的"麻烦",将会给我们的程序带来更大的"麻烦"。而程序中包含必要的异常处理,也是对一位优秀程序员的基本要求。

来源:http://www.anqn.com/dev/vc/2009-05-24/a09110607.shtml

SKySeraph Oct.4th HQU  zgzhoabo@gmail.com

转载于:https://www.cnblogs.com/skyseraph/archive/2010/10/24/1859898.html

Visual C++中的异常处理浅析[轉]相关推荐

  1. Visual C++中的异常处理浅析(上)

    Visual C++中的异常处理浅析 作者:宋宝华  e-mail:[email]21cnbao@21cn.com[/email] Visual C++提供了对C语言.C++语言及MFC的支持,因而其 ...

  2. Visual C++ 中的重大更改

    为什么80%的码农都做不了架构师?>>>    此文章由人工翻译. 将光标移到文章的句子上,以查看原文. 更多信息. Visual C++ 中的重大更改 Visual Studio ...

  3. c#异常处理_C#中的异常处理

    c#异常处理 What an exception is? 有什么例外? An exception is a runtime error; that means an abnormal situatio ...

  4. 在Visual C++中实现一个DLL木马

    在Visual C++中实现一个DLL木马 写一个木马挂载程序,听起来似乎很玄,其实也很简单,本文将告诉你怎样用Visual C++实现一个侵入木马的程序.本文主要涉及动态链接库(DLL)木马,目前相 ...

  5. 在Visual C++中如何利用UDL文件来建立ADO连接

    在Visual C++中如何利用UDL文件来建立ADO连接 使用通用数据连接文件(*.UDL,以下简称文件)来创建ADO连接,可以和ODBC一样可视化地定义要连接的数据源,从而实现数据访问的透明性. ...

  6. kotlin中的异常处理_如何使用assertFailsWith在Kotlin中测试异常

    kotlin中的异常处理 by Daniel Newton 丹尼尔·牛顿 如何使用assertFailsWith在Kotlin中测试异常 (How to test exceptions in Kotl ...

  7. 编写高质量代码改善C#程序的157个建议——建议86:Parallel中的异常处理

    建议86:Parallel中的异常处理 建议85阐述了如何处理Task中的异常.由于Task的Start方法是异步启动的,所以我们需要额外的技术来完成异常处理.Parallel相对来说就要简单很多,因 ...

  8. SpringMVC 中的异常处理

    SpringMVC 中的异常处理 异常处理的思路 系统中异常包括两类:预期异常和运行时异常 RuntimeException,前者通过捕获异常从而获取异常信息,后者主要通过规范代码开发.测试通过手段减 ...

  9. PHP如何进行错误与异常处理(PHP7中的异常处理和之前版本异常处理的区别)

    PHP如何进行错误与异常处理(PHP7中的异常处理和之前版本异常处理的区别) 一.总结 一句话总结: throwable接口+Error类 在PHP7更新中有一条:更多的Error变为可捕获的Exce ...

最新文章

  1. 中key的用途_Micro Focus Operations Bridge Manager中的多个(RCE)漏洞
  2. flutter环境搭建-完整版
  3. 组件 插件 控件联系与区别
  4. Django连接postgresql数据库
  5. 为什么人会摆高姿态_Yo , 你为什么喜欢冲浪?
  6. 编写运行java的步骤,【简答题】编写运行Java程序需要经过哪些主要步骤? (30.0分)...
  7. 常用的锂电池充电IC芯片
  8. java 实心圆,如何用css3实现实心圆
  9. 以太坊之dapp例子
  10. CSS3炫酷发光文字 ,自定义色彩
  11. 十大高颜值蓝牙耳机排行榜,最受欢迎的真无线蓝牙耳机前十名
  12. JS逆向之巨量创意signature签名
  13. 1N系列常用整流二极管的主要参数
  14. 日本机器人全球领先来自这三大顶尖技术
  15. ES--深分页Scroll
  16. IT_开发提测标准规范
  17. 利用Matlab进行图像的数字化
  18. Python3控制鼠标点击
  19. 软件工程第一次作业作业
  20. 终极反馈装置UFD-1.系统架构与设计制作步骤

热门文章

  1. oracle实现主键自增长及自动生成策略
  2. Mybatis Mapper自动注入警告的解决方案
  3. 【应用推荐】用狗屁不通文章生成器写文章
  4. c语言输入1显示good,求助 无论输入什么输出的结果是个0
  5. JavaScript文件存储信息对象cookie编码生存期
  6. python博客主题_博客园SimpleMemary主题美化教程
  7. linux symbol文件,[转] Linux文件系统之hard linksymbol link
  8. bootstrap 输入错误提示_网上体育用品商城(ssm,mysql,bootstrap,html,css)
  9. 生产者和消费者问题变形
  10. OpenGL基础5:第一个正方形