遍历路径下所有文件
//使用了递归函数的方法
//需要 #include <io.h> 头文件
//将所找到的所有格式的文件放到 FinallyFiles 容器下
//这个是只输出文件名
void TraversalFolder(string path,vector<string> &FinallyFiles)
{
vector<string> files;
string fileType = ".*";
// 文件句柄
intptr_t hFile = 0;
// 文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*" + fileType).c_str(), &fileinfo)) != -1)
{
do {
//保存文件的全路径 后面读取数据的时候还需要这个路径
//files.push_back(p.assign(path).append("\\").append(fileinfo.name));
files.push_back(fileinfo.name);
} while (_findnext(hFile, &fileinfo) == 0); //寻找下一个,成功返回0,否则-1
_findclose(hFile);
}
//删除当前目录和父级目录,一般这两个用不到所以删掉去除干扰
vector<string>::iterator it = files.begin();
while (it != files.end())
{
string AccessoryFile = *it;
if ((AccessoryFile == ".") || (AccessoryFile == "..")) { it = files.erase(it); }
else { it++; }
}
for (int i = 0; i < files.size(); i++)
{
if (files[i].find(".") != files[i].npos)
{
FinallyFiles.push_back(files[i]);
}
else
{
TraversalFolder(path + "\\" + files[i],FinallyFiles);
}
}
}
//这个是输出路径加文件名的版本
void TraversalFolder(string path,vector<string> &FinallyFiles)
{
vector<string> files;
string fileType = ".*";
// 文件句柄
intptr_t hFile = 0;
// 文件信息
struct _finddata_t fileinfo;
string p;
if ((hFile = _findfirst(p.assign(path).append("\\*" + fileType).c_str(), &fileinfo)) != -1)
{
do {
//保存文件的全路径 后面读取数据的时候还需要这个路径
files.push_back(p.assign(path).append("\\").append(fileinfo.name));
//files.push_back(fileinfo.name);
} while (_findnext(hFile, &fileinfo) == 0); //寻找下一个,成功返回0,否则-1
_findclose(hFile);
}
//删除当前目录和父级目录,一般这两个用不到所以删掉去除干扰
vector<string>::iterator it = files.begin();
while (it != files.end())
{
string AccessoryFile = *it;
if ((AccessoryFile.find("\\.") != AccessoryFile.npos) || (AccessoryFile.find("\\..") != AccessoryFile.npos)) { it = files.erase(it); }
else { it++; }
}
for (int i = 0; i < files.size(); i++)
{
if (files[i].find(".") != files[i].npos)
{
FinallyFiles.push_back(files[i]);
}
else
{
TraversalFolder(files[i],FinallyFiles);
}
}
}
assign方法可以理解为先将原字符串清空,然后赋予新的值作替换。
append函数是向string的后面追加字符或字符串。
C++中读取文件可以采用几个函数分别为,_findfirst、_findnext、_findclose。
其中还要借助结构体 struct _finddata_t,_finddata_t主要用来存储各种文件的信息。
struct _finddata64i32_t { // 定义成了 _finddata_t
unsigned attrib;
__time64_t time_create; /* -1 for FAT file systems */
__time64_t time_access; /* -1 for FAT file systems */
__time64_t time_write;
_fsize_t size;
char name[260]; //可以直接用结构体查询文件的名字
};
/结构体中参数的属性//
(1)其中 unsigned attribute (attrib) 表示文件的属性,分别有以下几种。
_A_ARCH(存档)、_A_HIDDEN(隐藏)、_A_NORMAL(正常)、_A_RDONLY(只读)、_A_SUBDIR(文件夹)、_A_SYSTEM(系统)
(2)time_t time_create:
这个time_create变量是用来存储 文件创建时间 的,time_t 类型本质是就是一个整型。
(3)time_t time_access:
文件最后一次被 访问的时间。
(4)time_t time_write:
文件最后一次被 修改的时间。
(5)char name[260]
文件名
(6)_fsize_t size:
文件的大小。这里的_fsize_t是unsigned long类型,表示文件的字节数。
long _findfirst( char *filespec, struct _finddata_t *fileinfo );
含义 : findfirst是一个计算机函数,功能是搜索与指定的文件名称匹配的第一个实例,若成功则返回第一个实例的句柄,否则返回-1L。
函数成功后,函数会把找到的文件的信息放入这个结构体中。
返回值 : 如果查找成功的话,将返回一个long型的唯一的查找用的句柄(就是一个唯一编号)。这个句柄将在_findnext函数中被使用。若失败,则返回-1。
参数 : filespec 标明文件的字符串,可支持通配符。比如:*.c,则表示当前文件夹下的所有后缀为C的文件。
fileinfo 这里就是用来存放文件信息的结构体的指针。就是这个 _finddata_t 需要声明一个结构体变量
int _findnext( long handle, struct _finddata_t *fileinfo );
含义 : 搜索磁盘目录; 取得下一个匹配的findfirst模式的文件
返回值 : 若成功返回0,否则返回-1。
参数 : handle 即由_findfirst函数返回回来的句柄。
fileinfo 文件信息结构体的指针。找到文件后,函数将该文件信息放入此结构体中。
int _findclose( long handle );
返回值 : 成功返回0,失败返回-1。
参数 : handle _findfirst函数返回回来的句柄。
//==========================================================================
//=============================另一个函数====================================
//==========================================================================
头文件要包含#include<io.h>
void findfile(string path,string mode)
{
_finddata_t file;
intptr_t HANDLE;
string Onepath = path + mode;
HANDLE = _findfirst(Onepath.c_str(), &file);
if (HANDLE == -1L)
{
cout << "can not match the folder path" << endl;
system("pause");
}
do {
//判断是否有子目录
if (file.attrib & _A_SUBDIR)
{
//判断是否为"."当前目录,".."上一层目录 首先查到的就是 . 和 .. 接下来才是文件名
if ((strcmp(file.name, ".") != 0) && (strcmp(file.name, "..") != 0))
{
string newPath = path +"\\" + file.name;
}
}
else
{
cout << file.name << " " << endl;
}
} while (_findnext(HANDLE, &file) == 0);
_findclose(HANDLE);
}
int main(int argc, char **argv)
{
string mode = "\\*.*";
string path = "F:\\duquceshi";
findfile(path,mode); //mode就是设置匹配的文件格式和名称
system("pause");
return 0;
}
时间戳日期时间(string类型)互换
#define _CRT_SECURE_NO_DEPRECATE
#include <iostream>
#include <ctime>
#include <string>
using namespace std;
//将时间戳转换成不同格式的时间
string StampTotdtime(time_t timeStamp)
{
char temp[100];
struct tm * timeSet = gmtime(&timeStamp); //把内容存储到结构体中
timeSet->tm_hour += 8;
strftime(temp, 50, "%Y-%m-%d %H:%M:%S", timeSet); //规定格式输出内容
return temp;
}
//将不同格式的时间转换成时间戳
time_t StdtimeToStamp(string timeStamp)
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
//这里的格式要和输入进来的字符串对应 比如参数是2020-01-21 09:18:46 那么对应的格式是 "%4d-%2d-%2d %2d:%2d:%2d"
// 参数是2020/01/21 09:18:46 那么对应的格式是 "%4d/%2d/%2d %2d:%2d:%2d"
sscanf_s(timeStamp.c_str(), "%4d-%2d-%2d %2d:%2d:%2d",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
tm.tm_year -= 1900;
tm.tm_mon--;
return mktime(&tm);
}
int main()
{
cout << StampTotdtime(1579569526) << endl; //2020-01-21 09:18:46
cout << StdtimeToStamp("2020-1-21 9:18:46") << endl; //1575043200
return 0;
}
//使用正则表达式 将时间转换成标准格式,然后再转换成时间戳 此程序在VS2010下运行成功
#include <iostream>
#include <string>
#include <regex>
#include <iterator>
using namespace std;
//将不同格式的时间转换成时间戳
time_t StdtimeToStamp(string timeStamp)
{
struct tm tm;
memset(&tm, 0, sizeof(tm));
//这里的格式要和输入进来的字符串对应 比如参数是2020-01-21 09:18:46 那么对应的格式是 "%4d-%2d-%2d %2d:%2d:%2d"
// 参数是2020/01/21 09:18:46 那么对应的格式是 "%4d/%2d/%2d %2d:%2d:%2d"
sscanf_s(timeStamp.c_str(), "%4d\\%2d\\%2d %2d:%2d:%2d",
&tm.tm_year, &tm.tm_mon, &tm.tm_mday,
&tm.tm_hour, &tm.tm_min, &tm.tm_sec);
tm.tm_year -= 1900;
tm.tm_mon--;
return mktime(&tm);
}
int main()
{
string str ="2018-9-11 9:9:0";
regex pattern2("(\\d{4})\\D*(\\d{1,2})\\D*(\\d{1,2})\\D*(\\d{1,2})\\D*(\\d{1,2})\\D*(\\d{1,2})\\D*");
string _time = regex_replace(str, pattern2,string("$1\\$2\\$3 $4:$5:$6"));
cout << StdtimeToStamp(_time) << endl;
return 0;
}
string 与 数值类型的转换
#include <sstream>
#include <iostream>
using namespace std;
//string 转换成数字
template <class Type>
Type stringToNum(const string& str) //这个不需要调精度
{
stringstream s;
Type num;
s << str;
s >> num;
return num;
}
//数字转换成 string
template <class Type>
string NumToString(Type a,int Precision = 10) //这里先默认精度为10
{
stringstream s;
//默认精度为6,精度 = 整数位数+小数位数
s.precision(Precision);
s << a;
string s1 = s.str();
return s1;
}
int main()
{
cout.precision(15); //cout输出也是有精度的,否则下面两个的输出会受到影响
cout << stringToNum<double>("100.0009123") << endl;
cout << NumToString<double>(1000.00009) << endl;
return 0;
}
消除黑窗
首先是能够消除控制台黑窗的代码
其实它还可以用在winform窗体上,只显示窗体并不显示控制台黑窗
#pragma comment( linker, "/subsystem:windows /entry:mainCRTStartup" )
//把这句加到代码的最上面,本着拿来主义先用
//但是它还是不能消除CMD的窗口弹出,在运行程序的时候还是会有一闪一闪的感觉
//system() 函数调用cmd会出现弹窗
//WinExec() 不阻塞,执行完cmd命令后就会继续执行后面的程序
//这是成功的一个小程序
//没有弹出黑窗的情况
#include <windows.h>
#include <ShellAPI.h> // ShellExecuteEx
#include <string>
#include <iostream>
using namespace std;
DWORD mySystem(const string& cmd, const string& par)
{
SHELLEXECUTEINFO ShExecInfo = { 0 };
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = NULL;
ShExecInfo.lpFile = cmd.c_str();//调用的程序名
ShExecInfo.lpParameters = par.c_str();//调用程序的命令行参数
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_HIDE;//窗口状态为隐藏
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo); //启动新的程序
DWORD finish;
finish = WaitForSingleObject(ShExecInfo.hProcess, INFINITE);等到该进程结束 执行成功返回值为0
return finish;
}
int main()
{
string par1 = "/c move C:\\Users\\Administrator\\Desktop\\源文件\\CC++与设计模式基础课程_讲义_v1.0.4.docx C:\\Users\\Administrator\\Desktop\\目标文件";
string par2 = "/c move C:\\Users\\Administrator\\Desktop\\目标文件\\CC++与设计模式基础课程_讲义_v1.0.4.docx C:\\Users\\Administrator\\Desktop\\源文件";
while (1)
{
mySystem("cmd.exe", par1, SW_SHOWMAXIMIZED);
Sleep(500);
cout << "源文件-> 目标文件" << endl;
mySystem("cmd.exe", par2, SW_SHOWMAXIMIZED);
Sleep(500);
cout << "目标文件->源文件" << endl;
}
return 0;
}
在winform上面使用这段程序的时候出现一些小插曲,特此记录下
原因在与 ShellExecuteEx() 默认设置的环境缺少库所以VS会报错 LNK2019 这一类的错误
解决方法 在链接器-输入-附加依赖项中加入 Advapi32.lib 即可
此问题曾困扰我一晚上
2019/12/16 再次更新
在使用vs2010 时用到ShellExecuteEx(),还是会提示不能解析命令
,看来光是Advapi32.lib还不够还需要这些库,虽然还不知道这些是什么
kernel32.lib
user32.lib
gdi32.lib
winspool.lib
comdlg32.lib
advapi32.lib
shell32.lib
ole32.lib
oleaut32.lib
uuid.lib
odbc32.lib
odbccp32.lib
InI文件的读取和写入
在项目中使用的配置文件用到了ini文件,.ini 文件是Initialization File的缩写,即初始化文件,是windows的系统配置文件所采用的存储格式。
使用感觉: 只适用于小量的配置参数,配置参数关系不复杂的情况
ini文件读取函数
//ini文件读取函数 没有找到文件会返回默认值
GetPrivateProfileString("123", "hero", "error", ch, sizeof(ch) - 1, "C:\\Users\\ThinkPad\\Desktop\\configfile.ini");
节名 变量值 错误 存储变量 容量 路径
//ini文件写入函数 没有文件会自动创建文件
WritePrivateProfileString("123", "hero", ch1,"C:\\Users\\ThinkPad\\Desktop\\configfile.ini");
节名 变量名 存储变量 路径
//获取ini一个小节的内容
GetPrivateProfileSection(LPCTSTR lpAppName,LPTSTR lpReturnedString,DWORD nSize,LPCTSTR lpFileName);
节名 存储变量 容量 路径
//在vs2010中只需要设置一下配置即可使用这个函数 也就是 字符集配置成多字符
//在vs2015中修改配置的方法似乎并不起效,需要在程序的第一行加上 #undef UNICODE 即可
//程序展示
#undef UNICODE
#include <iostream>
#include <string>
#include <Windows.h>
using namespace std;
int main()
{
char ch[] = "你好";
char rech[100];
WritePrivateProfileString("hero", "123",ch,".//configfile.ini");
GetPrivateProfileString("hero", "123", "error", rech, sizeof(rech) - 1, ".//configfile.ini");
cout << rech << endl;
return 0;
}
更深一步的探索后我们就要来了解ini文件的函数原型了,这里只是肤浅的认识
//ini文件 写函数原型
WINBASEAPI
BOOL
WINAPI
WritePrivateProfileStringW(
_In_opt_ LPCWSTR lpAppName,
_In_opt_ LPCWSTR lpKeyName,
_In_opt_ LPCWSTR lpString,
_In_opt_ LPCWSTR lpFileName
);
//ini文件 读函数原型
WINBASEAPI
DWORD
WINAPI
GetPrivateProfileStringW(
_In_opt_ LPCWSTR lpAppName,
_In_opt_ LPCWSTR lpKeyName,
_In_opt_ LPCWSTR lpDefault,
_Out_writes_to_opt_(nSize, return + 1) LPWSTR lpReturnedString,
_In_ DWORD nSize,
_In_opt_ LPCWSTR lpFileName
);
//写函数的返回值是 BOOL 变量,注意这个不是bool变量 不是bool变量,不是bool变量 ==> typedef int BOOL;
//但是读函数的返回值是 DWORD 变量 ==> typedef unsigned long DWORD;
//GetPrivateProfileString 的返回值是读取到字符串的个数
//WritePrivateProfileString 的返回值是是否写入成功 BOOL 是int型 bool是布尔型
弹窗
MessageBox(NULL,TEXT("读取配置参数表格文件出错,详情请查看系统日志,点击确定退出程序"),TEXT("提示"),MB_OK);
MessageBox::Show("导入结束","提示");
主函数传参
//C++的main函数可以没有输入参数,也可以有输入参数,而且只能有两个参数,习惯上coding如下:
int main(int argc, char* argv[]) 或者 int main(int argc, char** argv)
/*其中,argc = argument count :表示传入main函数的数组元素个数,为int类型
argv = argument vector :表示传入main函数的指针数组,为char**类型。
第一个数组元素argv[0]是程序名称,并且包含程序所在的完整路径。
argc至少为1,即argv数组至少包含程序名。*/
#include <iostream>
using namespace std;
int main(int argc, char* argv[])
{
for(int i=0;i<argc;i++)
cout<<argv[i]<<endl;
return 0;
}
/*一般编译器默认使用argc和argv两个名称作为main函数的参数,但这两个参数如此命名并不是必须的,你可以使用任何符合C++语言命名规范的变量名,但要保证第一个参数类型为int型,第二个参数为char**型,如下图所示。*/
#include <iostream>
using namespace std;
int main(int count, char* input_parameters[])
{
for(int i=0;i<count;i++)
cout<<input_parameters[i]<<endl;
return 0;
}
/*由于main函数不能被其他函数调用,因此不可能在程序内部取得实际值。main函数的参数值是从操作系统命令行上获取的。在window系统中,假如编译链接成的可执行文件为my_project.exe,则在命令提示符(快捷键windows+R,输入cmd)中,键入如下命令(可执行文件 参数 参数 参数 ...):*/
my_project.exe jisongxie 1996
/*将会传递三个参数给main函数,第一个argv[0]是前面提到的文件名,第二个argv[1]是"jisongxie",第三个argv[2]是“1996”。同理,可以传入更多的参数。在ubuntu系统中,可以通过终端进行相同的操作。
传入的参数数组类型为char *字符串类型,可以通过atoi,atof函数进行类型的转换。
1、atoi,即ascii to integer,把字符串转换成int
2、atof,即ascii to float,把字符串转换成double
3、atol,即ascii to long int,把字符串转换成long int
4、atoll,即ascii to long long int,把字符串转换成long long int
例如上述输入的1996,可以得到如下:
int year = atoi(argv[2]); // year = 1996
因此,通过上述的命令行输入以及程序里面的类型转换,可以通过命令行窗口传入值(字符串和数字)到程序中运行。*/
#include <iostream>
#include <Windows.h>
#include <stdio.h>
using namespace std;
int main(int argc, char* argv[])
{
int sleeptime;
if (argc == 2) // 最少一个参数
{
sleeptime = atoi(argv[1]); //argv[1] 是cmd中输入的第一个参数
}
else //没有两个参数的情况
{
cout << "执行程序" << endl;
}
Sleep(2000);
}
生成日志
自己写的简单的代码
//只是单纯的把我要写的东西写入到一个文件中(自动创建)并且在前面加上日期而已
#include <iostream>
#include <string>
#include <io.h>
#include <fstream>
#include <Windows.h>
using namespace std;
// logcontent是内容 logaddress 是地址
void Writelog(string LogContent, string LogAddress)
{
ofstream File;
SYSTEMTIME sys;
GetLocalTime(&sys);
char FileName[50];
char TimeMessage[50];
sprintf_s(FileName, "\\%u年%u月%u日 日志文件.txt", sys.wYear, sys.wMonth, sys.wDay);
LogAddress += FileName;
sprintf_s(TimeMessage, "[%02u:%02u:%02u:%03u] ", sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds);
File.open(LogAddress, ios::app);
if (File.is_open())
{ // 如果创建成功
File << TimeMessage << LogContent << endl; // 使用与cout同样的方式进行写入
}
File.close(); // 执行完操作后关闭文件句柄
}
int main()
{
Writelog("产生一个日志文件", "C:\\Users\\ThinkPad\\Desktop");
return 0;
}
//下面的是在linux下 用C写日志
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
/*
char * LogContent 日志的内容
char * LogAddress 日志的地址
*/
void Writelog(char * LogContent,char * LogAddress)
{
int Size = 100;
//获取当前时间
time_t rawtime;
struct tm *info;
time( &rawtime );
info = localtime( &rawtime );
char Datestr[Size]; //用日期的名称作为日志名字的一部分
char timeheader[Size]; //每一条日志前面的时间 时分秒
char filename[1024]; //最终要打开的文件名
char writestr[1024]; //要写的日志内容
strftime(Datestr,Size, "./%Y-%m-%d 日志文件", info);
strftime(timeheader,Size, "%H-%M-%S", info);
//向文件中写入日志
FILE * file = NULL;
sprintf(filename,"%s%s",LogAddress,Datestr);
file = fopen(filename,"a");
sprintf(writestr,"[%s] %s\n",timeheader,LogContent);
fputs(writestr,file);
fclose(file); // 执行完操作后关闭文件句柄
}
int main()
{
Writelog("123","./");
Writelog("123","./log/");
//向文件写入日志
return 0;
}
日志的格式规范
日志的作用
一般程序日志出自下面几个方面的需求:
1. 记录用户操作的审计日志,甚至有的时候就是监管部门的要求。
2. 快速定位问题的根源
3. 追踪程序执行的过程。
4. 追踪数据的变化
5. 数据统计和性能分析
6. 采集运行环境数据
一般在程序上线之后,一旦发生异常,第一件事就是要弄清楚当时发生了什么。用户当时做了什么操作,环境有无影响,数据有什么变化,是不是反复发生等,然后再进一步的确定大致是哪个方面的问题。确定是程序的问题之后再交由开发人员去重现、研究、提出解决方案。这时,日志就给我们提供了第一手的资料。
撰写日志的要求
既然撰写日志是有需求,而且也能在未来帮助我们提高工作效率的事情,长远来看是非常有利的一件事情。因此我们应该在自己开发的程序中符合规范的撰写日志,在写日志时要注意以下的问题。
日志的可读性
日志时给人读的,不仅仅是让自己明白,也要让没有接触过我们源代码的其他程序员也能够一目了然。有的同事在日志中打印特殊的标识符号,例如“++++++++++”, “===========”,“—————”,这些符号令人眼花缭乱。这是一种不好的编程习惯。
另外,把日志分类输出到不同的文件也有利于我们排除干扰,迅速找到我们需要的信息。而且,最好在打印日志时输出英文,防止中文不支持而打印出乱码的情况。
日志的性能
无论我们把日志写到文件还是数据库,都需要消耗IO资源。适当的控制日志的输出也有利于提高程序的性能。例如:尽量避免在在大的循环中打印意义不大的日志内容。输出日志之前最好能判断日志的级别(例如. debug前先调用isDebugEnabled()作出判断)。
占用磁盘空间
通常,我们都是把日志写入磁盘上的日志文件中。适当的使用滚动日志并且定时清除旧文件是有好处的。我见过这样一个例子,程序运行几次后就跑不起来了,前几次都是正常的。怎么都想不明白程序有什么问题,最后才发现居然是日志文件占满了磁盘空间。在实际的应用中出现上G的日志文件也往往不少见。要在这样规模的日志文件中找出对解决问题有用的信息也是一大挑战。
日志的时效性
有的时候我们并不能及时的发现问题。需要追溯之前的日志。所以我们是需要保留一段时间以内的日志便于追溯。
日志级别
通常我们在产品环境中日志的级别都在INFO以上,所以我们必须保证在这样的情况下程序仍然能够输出足够我们作出判断的信息。
等级由低到高:debug<info<warn<Error<Fatal;
debug 级别最低,可以随意的使用于任何觉得有利于在调试时更详细的了解系统运行状态的东东;
info 重要,输出信息:用来反馈系统的当前状态给最终用户的;
后三个,警告、错误、严重错误,这三者应该都在系统运行时检测到了一个不正常的状态。
warn, 可修复,系统可继续运行下去;
Error, 可修复性,但无法确定系统会正常的工作下去;
Fatal, 相当严重,可以肯定这种错误已经无法修复,并且如果系统继续运行下去的话后果严重。
什么时候使用 info, warn , error ?
info 用于打印程序应该出现的正常状态信息, 便于追踪定位;
warn 表明系统出现轻微的不合理但不影响运行和使用;
error 表明出现了系统错误和异常,无法正常完成目标操作。
总结起来, 错误日志格式可以为:
log.error(“[接口名或操作名] [Some Error Msg] happens. [params] [Probably Because]. [Probably need to do].”);
log.error(String.format(“[接口名或操作名] [Some Error Msg] happens. [%s]. [Probably Because]. [Probably need to do].”, params));
log.error(“[Some Error Msg] happens to 错误参数或内容 when [in some condition]. [Probably Because]. [Probably need to do].”);
log.error(String.format(“[Some Error Msg] happens to %s when [in some condition]. [Probably Because]. [Probably need to do].”, parameters));
日志内容
我们在写日志的时候,需要注意输出适当的内容。首先,尽量使用业务相关的描述。我们的程序是实现某种业务的,那么就最好能描述清楚这个时候走到了业务过程的哪一步。其次,避免在日志中输出一些敏感信息,例如用户名和密码。以及,要保持编码的一致。如果不能保证就尽量使用英文而不是中文。这样当我们拿到日志之后就不会因为看到一堆乱码而不知所云了。
常见的日志格式中对于每一条日志应含有的信息包括日期、时间、日志级别、代码位置、日志内容、错误码等信息。下面是一个工作中的日志文件的一部分内容:
日期 时间 日志级别 代码位置 错误码 日志内容
2018-05-22 15:35:53.850 TRACE TDWZLog [0x00001b10] <36> <TDWZProtocol::Init>,TDWZProtocol::Init
2018-05-22 15:35:53.850 TRACE TDWZLog [0x00001b10] <89> <TDWZProtocol::Init>,End in processing TDWZProtocol::Init
2018-05-22 15:35:53.853 TRACE TDWZLog [0x00001b10] <142> <TDWZProtocol::Connect>,Connect Execute finish
2018-05-22 15:35:53.853 TRACE TDWZLog [0x00002f10] <149> <GetAlarmEventPro>,Enter GetAlarmEventPro func
2018-05-22 15:39:37.502 TRACE TDWZLog [0x00002f10] <225> <GetAlarmEventPro>,End Get AlarmEventPro Func
2018-05-22 15:39:37.503 TRACE TDWZLog [0x000029fc] <241> <TDWZProtocol::DisConnect>,close socket
错误代码位置
在C/C++编译时,可以使用__FUNCTION__、__FILE__和__LINE__等编译器内置宏定义获得运行函数、文件及行数等信息。
__FUNCTION__ 能够获取所在函数名
__FILE__ 能够获取所在文件名
__LINE__ 能够获取所在行数
我感觉这三个就够用了
//使用 sprint_s函数
#include <stdio.h>
int main()
{
printf("%d\n",);
char errorline[100];
char errorfile[100];
char errorfun [100];
string str1;
sprintf_s(errorline,"错误行数: %d ",__LINE__);
str1 += errorline;
sprintf_s(errorfile,"错误文件: %s ",__FILE__);
str1 += errorfile;
sprintf_s(errorfun,"错误函数: %s ",__FUNCTION__);
str1 += errorfun;
cout << str1 ;
return 0;
}
#include <iostream>
#include <string>
#include <io.h>
#include <fstream>
#include <Windows.h>
using namespace std;
// logcontent是内容 logaddress 是地址
// 需要try catch 配合使用
void Writelog( string LogContent,
string LogAddress,
const char * errorline = NULL,
const char * errorfun = NULL,
const char * errorfile = NULL
)
{
ofstream File;
SYSTEMTIME sys;
GetLocalTime(&sys);
char FileName[100] = "";
char TimeMessage[100] = "";
char LocationMessage[300] ="";
char ErrorLine[100] = "";
char ErrorFun[100] = "";
char ErrorFile[100] = "";
if (errorline != NULL && errorfun != NULL && errorfile != NULL)
{
strcpy_s(ErrorLine, errorline);
strcpy_s(ErrorFun, errorfun);
strcpy_s(ErrorFile, errorfile);
sprintf_s(LocationMessage, "行数:%s 函数:%s 文件:%s ", ErrorLine, ErrorFun, ErrorFile);
}
sprintf_s(FileName, "\\%u年%u月%u日 日志文件.txt", sys.wYear, sys.wMonth, sys.wDay);
LogAddress += FileName;
sprintf_s(TimeMessage, "[%02u:%02u:%02u:%03u] ", sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds);
File.open(LogAddress, ios::app);
if (File.is_open())
{ // 如果创建成功
File << TimeMessage << LocationMessage <<"日志内容: "<<LogContent << endl; // 使用与cout同样的方式进行写入
}
File.close(); // 执行完操作后关闭文件句柄
}
int main()
{
char errorline[100];
char errorfile[100];
char errorfun[100];
string str1;
sprintf_s(errorline, "%d", __LINE__);
str1 += errorline;
sprintf_s(errorfile, "%s", __FILE__);
str1 += errorfile;
sprintf_s(errorfun, "%s", __FUNCTION__);
str1 += errorfun;
Writelog("产生一个日志文件", "C:\\Users\\ThinkPad\\Desktop",errorline,errorfun,errorfile);
Writelog("产生一个日志文件", "C:\\Users\\ThinkPad\\Desktop");
return 0;
}
CSV文件的行数列数查询
//获取文件行数
int Getdatarow(string address)
{
string line;
fstream File;
int rownumber = 0;
File.open(address, ios::in);
if (!File) //对文件打开成功做出判断
{
return -1;
}
while (!File.eof())
{
getline(File, line);
rownumber++;
}
File.close();
return rownumber;
}
//获取文件列数 也可以获取文件行数
int Getdataline(string address)
{
string line;
ifstream File;
vector<int> vline;
File.open(address, ios::in);
if (!File) //判断文件打开是否成功
{
vline.push_back(-1);
return vline;
}
while (!File.eof())
{
int linenumber = 0;
getline(File, line);
int number = (int)strlen(line.c_str());
string strchar;
strchar.assign(line.c_str());
for (int i = 0; i < number; i++)
{
if (strchar[i] == ',')
{
linenumber++;
}
}
vline.push_back(linenumber+1);
}
File.close();
return vline.size();
}
CSV即Comma Separate Values,这种文件格式经常用来作为不同程序之间的数据交互的格式。
具体文件格式
1.每条记录占一行 以逗号为分隔符
2.逗号前后的空格会被忽略
3.字段中包含有逗号,该字段必须用双引号括起来
4.字段中包含有换行符,该字段必须用双引号括起来
5.字段前后包含有空格,该字段必须用双引号括起来
6.字段中的双引号用两个双引号表示
7.字段中如果有双引号,该字段必须用双引号括起来
8.第一条记录,可以是字段名
按照特定字符分割字符串
//一般我用做截取目录
//需要的头文件
#include <sstream>
vector<string> Split(string str, char del)
{
stringstream ss(str);
string tok;
vector<string> ret;
while (getline(ss, tok, del))
{
ret.push_back(tok);
}
return ret;
};
记录当前的时间
//sys变量中还有年月日
//需要的头文件
#include <Windows.h>
string CurrentTime()
{
string TimeMessage;
char timemessage[20];
SYSTEMTIME sys;
GetLocalTime(&sys);
sprintf_s(timemessage, "[%02u:%02u:%02u.%03u]", sys.wHour, sys.wMinute, sys.wSecond, sys.wMilliseconds);
TimeMessage = timemessage;
return TimeMessage;
}
移动指定的文件到文件夹(不会有cmd黑窗)
//scr是文件路径 target是目标文件夹
//属性中需要将字符集 改为 使用多字节字符集
//需要的头文件
#include <Windows.h>
bool Movefile(string scr, string target)
{
string par;
par = "/c move " + scr + " " + target;
SHELLEXECUTEINFO ShExecInfo = { 0 };
ShExecInfo.cbSize = sizeof(SHELLEXECUTEINFO);
ShExecInfo.fMask = SEE_MASK_NOCLOSEPROCESS;
ShExecInfo.hwnd = NULL;
ShExecInfo.lpVerb = NULL;
ShExecInfo.lpFile = "cmd.exe";//调用的程序名
ShExecInfo.lpParameters = par.c_str();//调用程序的命令行参数
ShExecInfo.lpDirectory = NULL;
ShExecInfo.nShow = SW_HIDE;//窗口状态为隐藏
ShExecInfo.hInstApp = NULL;
ShellExecuteEx(&ShExecInfo); //启动新的程序
if (WaitForSingleObject(ShExecInfo.hProcess, INFINITE) == 0)等到该进程结束 执行成功返回值为0
{
return true;
}
else
{
return false;
}
}