目录
日志
相关函数
time函数
localtime函数
va_list类型
vsnprintf函数
宏支持可变参数
__FILE__和__LINE__
完整代码
Log.hpp
标记黏合操作符##(重点)
LockGuard.hpp
日志
基本概念:用于记录软件运行时的信息,可以向显示器和文件中进行打印,且有特定的格式
日志格式:[日志等级] [PID] [FileName] [FileNum] [Time] 日志内容(支持可变参数)
- PID:产生日志的当前进程的PID
- FileName:日志所存放的文件名称
- FileNum:新增的日志信息在该文件的行数
- Time:新增日志出现的时间
日志等级:DEBUG(因调式而产生的)、INFO(常规输出,比如打印某文件成功)、WARNING(有问题但不影响程序执行)、ERROR(很严重但程序仍然勉强能运行)、FATAL(致命的,程序无法运行)
相关函数
time函数
包含头文件:<time.h>
函数原型:time_t time(time_t *tloc)
- tloc:一个指向
time_t
类型的指针。如果不为NULL
,函数还会将当前时间保存到该指针指向的内存中。如果为NULL
,则不进行存储操作
功能:获取当前的时间
返回值:当前的日历时间
#include <stdio.h>
#include <time.h>
int main() {
time_t current_time;
// 获取当前时间,并存储在 current_time 中
time(¤t_time);
// 打印当前时间(以秒为单位)
printf("Current time in seconds since 1970: %ld\n", current_time);
// 使用 ctime 函数将时间转换为可读字符串
printf("Current local time: %s", ctime(¤t_time));
return 0;
}
localtime函数
包含头文件:<time.h>
函数原型:struct tm *localtime(const time_t *timep);
功能:将给定的 time_t
时间转换为表示本地时间的 struct tm
结构体
返回值:成功时返回指向 struct tm
结构的指针,该结构包含了本地时间的详细信息,如年、月、日、小时、分钟、秒等,失败时返回NULL(比如timep无效)
struct tm {
int tm_sec; // 秒,范围从 0 到 60(包含闰秒)
int tm_min; // 分钟,范围从 0 到 59
int tm_hour; // 小时,范围从 0 到 23
int tm_mday; // 一月中的第几天,范围从 1 到 31
int tm_mon; // 月份,范围从 0 到 11(0 代表 1 月)
int tm_year; // 自 1900 年起的年份
int tm_wday; // 一周中的第几天,范围从 0 到 6(0 代表星期天)
int tm_yday; // 一年中的第几天,范围从 0 到 365
int tm_isdst; // 夏令时标志,正值代表启用了夏令时,0 代表未启用,负值表示未知
};
#include <stdio.h>
#include <time.h>
int main() {
time_t current_time;
struct tm *time_info;
// 获取当前时间
time(¤t_time);
// 将时间转换为本地时间
time_info = localtime(¤t_time);
// 打印本地时间
printf("当前本地时间: %s", asctime(time_info));
return 0;
}
va_list类型
包含头文件:<stdarg.h>
基本概念:C 语言提供了一组宏和数据类型来帮助我们在函数中处理可变参数,常用的宏有 va_list
、va_start
、va_arg
和 va_end
va_list:
用于声明一个保存可变参数信息的变量。va_start:
用于初始化va_list
变量,以便后续的可变参数可以被访问va_arg:
用于获取下一个可变参数的值va_end:
结束对可变参数的访问,清理va_list
注意事项:
va_arg
必须知道每个参数的类型,因为它无法自动识别参数的类型- 必须确保
va_start
和va_end
成对使用,否则可能会导致内存泄漏或其他未定义行为
使用步骤:
- 定义一个函数,形参列表中需要一个固定参数(通常称为“最后一个确定参数”),可变参数将出现在它之后。
- 声明一个
va_list
变量。 - 使用
va_start
初始化va_list
,并指定最后一个固定参数。 - 使用
va_arg
获取下一个可变参数。 - 使用
va_end
结束可变参数的处理
#include <stdio.h>
#include <stdarg.h>
// 可变参数函数,计算传入参数的总和
int sum(int count, ...) {
va_list args; // 声明 va_list 变量
int total = 0;
va_start(args, count); // 初始化 va_list,传入最后一个固定参数
// 通过循环提取每个参数
for (int i = 0; i < count; i++) {
total += va_arg(args, int); // 获取下一个参数并累加
}
va_end(args); // 清理 va_list
return total;
}
int main() {
int result = sum(4, 1, 2, 3, 4); // 传入 4 个参数
printf("总和: %d\n", result); // 输出总和 10
return 0;
}
注意事项:最后一个固定参数是第一个形参,因为函数参数压栈顺序是从右向左(所以上面va_start的第二个参数为count)
vsnprintf函数
包含头文件:<stdarg.h>
函数原型:int vsnprintf(char *str, size_t size, const char *format, va_list ap);
str:
指向要存储生成字符串的缓冲区(字符数组)size:
缓冲区的大小(即最多写入size-1
个字符,并自动在末尾添加空字符\0
)format:
格式字符串,类似printf()
函数的格式字符串,用于描述如何格式化输出ap:
类型为va_list
的可变参数列表,它是由va_start
宏初始化的
功能:格式化输出到字符串(省去了va_list类型例子中的循环提取的部分)
返回值:成功时返回生成的格式化字符串的长度(不包括结尾的 \0
字符),如果返回值大于或等于 size
,则说明输出被截断。失败时返回负值
#include <stdio.h>
#include <stdarg.h>
void my_vsnprintf(char *buffer, size_t size, const char *format, ...) {
va_list args;
va_start(args, format);
// 使用 vsnprintf 将格式化内容写入 buffer
vsnprintf(buffer, size, format, args);
va_end(args);
}
int main() {
char buffer[100];
// 调用自定义函数,格式化字符串并传入参数
my_vsnprintf(buffer, sizeof(buffer), "Hello %s, your score is %d", "Alice", 95);
// 打印生成的字符串
printf("%s\n", buffer); // 输出: Hello Alice, your score is 95
return 0;
}
宏支持可变参数
基本概念:在 C 语言中,通过使用__VA_ARGS__
关键字,可以使得宏支持可变参数,该可变参数宏允许宏接收任意数量的参数,并且可以将这些参数传递给宏体中的代码
//替换后 替换前
#define MACRO_NAME(fixed_arg, ...) macro_name(fixed_arg,__VA_ARGS__)
fixed_arg:
固定参数,必须提供__VA_ARGS__:
可变参数,可以是任意数量的其他参数
//一个可以打印不同数量参数的宏
#include <stdio.h>
#define PRINTF(fmt, ...) printf(fmt, __VA_ARGS__)
//本例子中的fixed_arg是格式化符
int main()
{
PRINTF("Hello %s, your score is %d\n", "John", 90);
PRINTF("The result is %f\n", 3.1415);
return 0;
}
__FILE__和__LINE__
__FILE__:在代码的任何地方使用 __FILE__
,都会返回该文件的完整路径或者文件名(编译时进行填充)
__LINE__:在代码的任何地方使用 __LINE__
,都会返回当前代码在文件中的行号(编译时进行填充)
EnableScreen();//在屏幕上打印
LOG(DEBUG,"hello %d, world %c, hello: %f\n",1000,'A',3.14);//日志级别是DEBUG
//宏替换后的结果:
do { lg.LogMessage(__FILE__, __LINE__, DEBUG, "hello %d, world %c, hello: %f\n", 1000, 'A', 3.14); } while(0)
//lg.LogMessage(文件名, 行号, 日志级别, 在尝试调用日志信息时自家加的可变参数,由调用时决定);
完整代码
Log.hpp
#pragma once
#include <iostream>
#include <unistd.h>
#include <ctime>
#include <cstdarg>
#include <fstream>
#include <cstring>
#include <pthread.h>
#include "LockGuard.hpp"
namespace log_ns
{
enum
{
DEBUG = 1,
INFO,
WARNING,
ERROR,
FATAL
};
//将日志等级转换为字符串
std::string LevelToString(int level)
{
switch(level)
{
case DEBUG:
return "DEBUG";
case INFO:
return "INFO";
case WARNING:
return "WARNING";
case ERROR:
return "ERROR";
case FATAL:
return "FATAL";
default:
return "UNKNOWN";
}
}
//获取当前时间(年、月、日)
std::string GetCurrentTime()
{
time_t now = time(nullptr);
struct tm *curr_time = localtime(&now);
char buffer[128];
snprintf(buffer,sizeof(buffer),"%d-%02d-%02d %02d:%02d:%02d",
curr_time->tm_year + 1900,//系统提供的是当前时间减去1900年的时间,实际使用时需要加上1900
curr_time->tm_mon + 1,//系统提供的日期是[0,11],所以也要加上1
curr_time->tm_mday,
curr_time->tm_hour,
curr_time->tm_min,
curr_time->tm_sec);
return buffer;
}
class logmessage//日志信息类
{
public:
std::string _level;//日志等级
pid_t _id;//产生日志的当前进程的PID
std::string _filename;//存放日志所存放的文件名称
int _filenumber;//新增的日志信息在该文件的行数
std::string _curr_time;//新增日志出现的时间
std::string _message_info;//日志内容
};
#define SCREEN_TYPE 1//向显示器上打为1
#define FILE_TYPE 2//向文件上打为2
const std::string glogfile = "./log.txt";//打印日志到当前目录下的log.txt文件
pthread_mutex_t glock = PTHREAD_MUTEX_INITIALIZER;//加锁保护日志的打印
class Log
{
public:
//向显示器或文件中打印的构造函数(提供了缺省值,保证永远都能打印到一个文件中,想要更改文件的路径就再定义一个就行)
Log(const std::string &logfile = glogfile):_logfile(logfile),_type(SCREEN_TYPE)
{
}
void Enable(int type)
{
_type = type;
}
void FlushLog(const logmessage &lg)
{
LockGuard lockguard(&glock);//加解锁
switch(_type)
{
case SCREEN_TYPE:
FlushLogToScreen(lg);
break;
case FILE_TYPE:
FlushLogToFile(lg);
break;
}
}
void FlushLogToScreen(const logmessage &lg)
{
printf("[%s][%d][%s][%d][%s] %s\n",
lg._level.c_str(),
lg._id,
lg._filename.c_str(),
lg._filenumber,
lg._curr_time.c_str(),
lg._message_info.c_str()
);
}
void FlushLogToFile(const logmessage &lg)
{
std::ofstream out(_logfile,std::ios::app);//向文件中打印时是以追加的方式进行的
if(!out.is_open()) return;//打开文件失败则直接返回
char logtxt[2048];
snprintf(logtxt,sizeof(logtxt),"[%s][%d][%s][%d][%s] %s",
lg._level.c_str(),//全部转换为c语言的字符串形式
lg._id,
lg._filename.c_str(),
lg._filenumber,
lg._curr_time.c_str(),
lg._message_info.c_str()
);
out.write(logtxt,strlen(logtxt));
out.close();
}
//填写日志消息
void LogMessage(std::string filename,int filenumber,int level,const char *format,...)
{
logmessage lg;
lg._level = LevelToString(level);
lg._filename = filename;
lg._filenumber = filenumber;
lg._id = getpid();
lg._curr_time = GetCurrentTime();
//提取可变参数
va_list ap;
va_start(ap,format);
char log_info[1024];
vsnprintf(log_info,sizeof(log_info),format,ap);
va_end(ap);
lg._message_info = log_info;
//打印日志(即打印logmessage类型的对象)
FlushLog(lg);
}
~Log(){};
private:
int _type;//确定要向哪里打印
std::string _logfile;//要打印的文件的名称
};
Log lg;
//__FILE__, __LINE__, level,这三个都是固定好的,在调用LOG的时候会直接将__FILE__和__LINE__进行填充,所以LOG宏的第一个参数是level
#define LOG(level, Format, ...) do { lg.LogMessage(__FILE__, __LINE__, level, Format, ##__VA_ARGS__); } while(0)
#define EnableScreen() do { lg.Enable(SCREEN_TYPE); } while(0)
#define EnableFILE() do { lg.Enable(FILE_TYPE); } while(0)
}
注意事项:LOG宏函数定义中应该是##__VA_ARGS__ 而不是__VA_ARGS__,如果是前者如果在调用LOG宏函数时没有使用可变参数比如:
LOG(FATAL,"socket error\n");
宏展开后就会多一个逗号,进而产生报错:
lg.LogMessage(__FILE__, __LINE__, FATAL, "socket error\n", );
标记黏合操作符##(重点)
基本概念:当在宏中使用可变参数(__VA_ARGS__
)时,如果不传递可变参数,可能会出现多余的逗号。##
可以用来处理这种情况,避免生成错误的代码。例如:
#define LOG(level, Format, ...) printf(Format, ##__VA_ARGS__)
int main() {
LOG("INFO", "Hello, World\n"); // 没有额外参数
LOG("INFO", "Value: %d\n", 42); // 带有一个额外参数
}
解释:在这个例子中,LOG("INFO", "Hello, World\n")
不带可变参数,但如果没有 ##__VA_ARGS__
,宏展开后可能会生成一个多余的逗号,如 printf("Hello, World\n", );
,这将导致语法错误。使用 ##
后,如果 __VA_ARGS__
是空的,前面的逗号也会被移除,避免错误
LockGuard.hpp
#pragma once
#include <pthread.h>
class LockGuard
{
public:
LockGuard(pthread_mutex_t *mutex):_mutex(mutex)
{
pthread_mutex_lock(_mutex);
}
~LockGuard()
{
pthread_mutex_unlock(_mutex);
}
private:
pthread_mutex_t *_mutex;
};
~over~