起因
今天在梳理日终结算平台的业务代码,整个系统基于MFC框架开发,对MFC的接触还要追溯到3年前读研期间的徐工项目,那段在徐工出差的日子也颇令人难忘。
项目比较古老,整个开发环境是vc6,由于对整个业务框架的不熟悉,以及对MFC的陌生,再加上对vc6环境的难以适应,我这只菜鸡只能想到,在相应的动作下fprintf一些重要的信息到文件中查看。但频繁的FILE *fp ,fopen 很烦(说实话 一遇到 c++ 文件操作 我就跳过,是该找时间复习一下c++的文件操作了)。
加上,每次想看的变量不同,所以联想到可变参数的使用,之前对此操作也只停留到了解,但并没有使用过,查找相关资料,自己写了一个logtest类。
用到的相关知识点:
函数的变参
获取本地时间
auto_ptr的使用 – (vc6.0 并不支持 c++11)
代码设计
言归正传,以下是logtest类的代码设计
logtest.h
#include <cstdio>
#include <memory>
class logtest
{
public:
logtest(char *path = "");
void createlog(char * contents,...);
void overlog();
~logtest();
private:
char * _path;//路径
FILE * _fp;
};
logtest.cpp
#include "logtest.h"
#include <cstring>
#include <ctime>
#include <iostream>
#include <cstdarg>
logtest::logtest(char *path)
{
//获取本地时间
time_t t = time(0);
char tmp[40]={NULL}; //40个字节 应该够了
strftime(tmp, sizeof(tmp), "_%Y%m%d_%H_%M_%S",localtime(&t));
//将本地时间与路径进行拼接
_path = new char[strlen(path)+1+40];
strcpy(_path,path);
strcat(_path,tmp);
}
void logtest::createlog(char * contents,...)
{
va_list ap;//可变参数列表 即 ...
char *tmpAp;
_fp = fopen(_path,"w+");
//va_start 本质:从contents 开始 (不包含contents,) ...的第一个参数
va_start(ap,contents);
tmpAp = contents;// 保存第一个,第一个也是要输出的。
while(tmpAp)
{
char * _text = new char[strlen(tmpAp)+1];
//对于传入的内容不确定,我把分配的堆空间全部清0
memset(_text,0,strlen(tmpAp)+1);
strcpy(_text,tmpAp);
fprintf(_fp,"%s\n",_text);
//释放空间
delete [] _text;
_text = NULL;
//va_arg 取得后一个参数 -- 这是错误的理解
//参看va_arg(ap,v) 源码后,得到当前参数 的内容,但是ap已经指向了下一个参数 (*(char *)(arg+=4) -4);
tmpAp = va_arg(ap, char*);
}
va_end(ap);
}
void logtest::overlog()
{
fclose(_fp); //并没有丢到析构函数中,手动释放文件句柄,相对灵活一些。
}
logtest::~logtest()
{
delete[] _path;
_fp = NULL;
}
va_start,va_arg,va_end 必须配套使用。
坑一:va_start
va_start(ap,contents) 并不是从contents 开始,而是从contents 后一个参数开始。
之前参照的代码 大概的意思是,循环取 […] 中的参数,与contents进行拼接,而我的需求是 从contents开始,包括contents ,循环往文件中输出。
起初代码,createlog:
char * tmp;
va_start(ap,conntents);
while(tmp = va_arg(ap,char*))
{
...
}
所以 contents是无法操作的,如果对于 使用 createlog(test); 直接输出为空
对于使用 create(test,test2,NULL) 只能输出第二个test2
#include <iostream>
#include "logtest.h"
using namespace std;
int main(int argc, char *argv[])
{
char * test = "for test1";
char * test2 = "for test2";
auto_ptr<logtest> ap(new logtest("C:\\FDlog\\debug"));//丢到智能指针里
ap->createlog(test,test2);
ap->overlog();
return 0;
}
因为对业务的不熟悉,及对代码的陌生。并没有将释放文件句柄 放到析构函数中。
同时用智能指针管理 logtest对象,我想更为稳妥一些。
坑二: 哨兵 NULL
main函数测试
#include <iostream>
#include "logtest.h"
using namespace std;
int main(int argc, char *argv[])
{
char * test = "for test1";
char * test2 = "for test2";
auto_ptr<logtest> ap(new logtest("C:\\FDlog\\debug"));
//NULL 必不可少
ap->createlog(test,test2,NULL);
ap->overlog();
return 0;
}
在调试的过程中,忘记在 createlog 成员函数中加入 NULL 哨兵,导致输出乱码。日终结算程序生成基金文件失败。(但并没有卡死,系统本身所出输出的日志文件缺失,只打印到数据库连接成功就结束)
在qt createor 4.01版本中测试:
- ap->createlog(test) 是没有问题的
在vc6 环境中
- ap->create(file_type) 是出现bug的
- 必须加上NULL哨兵参数才可以
这周五晚上学习一下va_list、va_start、va_arg、va_end 的源码。