在线时间:8:00-16:00
迪恩网络APP
随时随地掌握行业动态
扫描二维码
关注迪恩网络微信公众号
几乎所有高级语言都实现了一个format函数用于处理不同类型的数据组合转换为字符串。 delphi中有format,FormatBuf,FmtStr等,VC中有sprintf,CString中的format等,都是相当常用且方便的函数。 这些函数使用起来与普通函数最大的区别就是其中一个参数的个数、类型、是不确定的,反过来说,就是参数是可变的,这个特点使得该类函数功能变得异常强大,当然也极其方便。我们如何在自己的代码中实现类似这样的函数呢?
1.VC实现变参函数 这个特殊的参数在VC中使用“...”表示,下面先来一段代码: using namespace std; DWORD AddMsgf(char* sMsgFormat, ...) //下面可以忽略 //s上面函数实际上并不高级,因为其实是借助vsprintf_s实现的,但是其原理已经差不多清晰了。 首先声明一个可以处理sMsgFormat的va_list数组。 再通过 va_start宏将你传进该函数的参数变量取得并存到va_list数组中; 这里面的sMsgFormat 作用就是告诉va_start如何取得sMsgFormat后面的所有参数变量地址的。 而参数是如何取得呢? 正常来说,我们使用函数参数无非就是直接使用该变量,而实际上对于CPU和进程来说,是通过将参数推入栈后,通过栈顶指针来取得具体参数地址或参数值,我们能够直接使用参数变量是因为编译器已经给你处理好了,而va_start就是在做编译器帮你处理好的工作,假如函数声明时“...”不是作为第二个参数,而是第三个,同时第二个参数是其他,比如下面这样: DWORD AddMsgf(char* sMsgFormat,DWORD dwVal, ...),那我们应该如何处理呢? 这是va_start宏传入的就是 dwVal,而不是 sMsgFormat了,通常情况下因为32位CPU的地址对齐功能会使得一个参数地址只占4字节,所以使用va_start宏是能够满足几乎所有情况的,而C语言如果在其他16位机子或者嵌入式开发时需要具体根据情况决定了,但是原理就是根据参数"..."前面一个参数的地址,获得后面整个栈的地址内容存放到va_list数组中。 参数取得后,这里偷懒,直接使用vsprintf_s处理了,因为vsprintf_s接受va_list参数,同时内部会根据sMsgFormat来取得实际参数,如遇到%d,就取4字节内容,遇到%s,就取连续字符直到0x0。 原理就是如此, 你也可以自己通过处理 va_list里存储的参数来实现自己的函数功能,那时候就不需要 vsprintf_s,而是完完整整属于自己的变参函数,最后处理完成不要忘了使用 va_end释放数组。
2.Delphi实现变参函数 delphi实现变参函数是通过Array of const类型的参数来实现的,这个类型是一个TVarRec数组。 TVarRec 结构体是实现类似泛型变量功能的结构体的,具体如下: PVarRec = ^TVarRec; 该类型看起来成员非常多,其实实际数据只有2个成员, record中使用cases是实现类型VC结构体中union联合体的功能的, 注意到其中一行: vtInteger: (VInteger: Integer; VType: Byte); 其他成员都是与VInteger成员使用同一个地址的,而且大小都不大于 Integer类型。 这一行代码中第二个成员是这个结构体的关键成员:VType成员,该成员的值由编译器内部实现赋值的。 VType成员的值对应着case Byte of 中的Byte取值,具体有: vtInteger = 0; 以上几乎包括了Delphi所有基本数据类型,也就是说,根据VType,我们可以直接判断数组成员的数据类型。 看到这里,应该可以联系到VC中的va_list,该 TVarRec数组其实与VC中的va_list功能是类似的。 VC中的va_list的成员数据类型需要由 va_start函数来实现赋值,因此假如你传入的参数个数与字符串sMsgFormat中的%个数不一致,会导致内存错误。如你sprintf使用的参数是“Test%d,%d”,而实际变长参数却只传入一个1,那么 vsprintf_s根据 “Test%d,%d”会去栈里取第二个"%d"的参数,而实际中栈却只有一个参数,结果直接导致栈指针超过了,这时栈顶指针已经错误了,将会导致后面所有代码执行报错,造成栈内存溢出。 而Delphi则由编译器自动实现代码处理好的,不是根据“Test%d,%d”取参数的,因此安全很多,具体可以查看CPU代码,这里就不贴了,Deilphi直接在传入参数之前一步就避免了通过栈来取参数的麻烦,因为传的是Array of const,是一个动态数组,对于该函数来说其实就是一个指针而已,因此,我们直接处理 Array of const会比VC处理 va_list相对来说方便一些。 下面是一段代码: function AddMsgF(param:Array of const):string; var i: integer; begin result := ''; if Length(param)=0 then Exit; for I:=0 to High(param) do with param[I] do case VType of vtInteger: result := result + inttostr(VInteger); //下面其他类型不做处理了 end; end; 其实实现这种函数不是难点,因为难点都被人实现并封装好了,或者已经被编译器处理好了,这些与其说实现,不如说使用,不过知道如何使用,也算是一种技术吧。
|
2023-10-27
2022-08-15
2022-08-17
2022-09-23
2022-08-13
请发表评论