当前位置: 首页 > news >正文

c++中如何利用VA_LIST 和单体模式,构建自己的log小系统

        在前文“利用c语言中宏定义及宏替换特性构建自己的log输出_py_free的博客-CSDN博客”,利用宏定义及宏替换,可以实现日志信息屏幕输出,但通常我们在实际项目中:

        【1】需要将日志信息记录在文件中

        【2】日志记录顺序需要得到保证

        【3】日志信息是不确定的,需要支持到不确定参数集输入

        鉴于以上三点,来设计一个日志小系统

一、日志小系统设计知识点

        在c/c++中VA_LIST是解决变参问题的一组宏,所在头文件:#include <stdarg.h>,通常用法是:

        (1)首先在函数里定义一具VA_LIST型的变量,这个变量是指向参数的指针;
        (2)然后用VA_START宏初始化刚定义的VA_LIST变量;
        (3)然后用VA_ARG返回可变的参数,VA_ARG的第二个参数是你要返回的参数的类型(如果函数有多个可变参数的,依次调用VA_ARG获取各个参数);
        (4)最后用VA_END宏结束可变参数的获取。

void myprint(const char* lpszFormat, ...)
{
	va_list args;
	char   szBuffer[2048] = {0};
	va_start(args, lpszFormat);
    #ifdef WIN32
	vsnprintf_s(szBuffer,sizeof(szBuffer), lpszFormat, args);
    #else
	vsnprintf(szBuffer, sizeof(szBuffer), lpszFormat, args);
    #endif
	va_end(args); 
    //printf(szBuffer);
}

         在c/c++中,单体实例实现主要就是将类的构造、拷贝构造函数等隐藏起来,使其仅在第一次实例化时可以创建实例,后续其每次实例化或使用时,仅提供或指向其第一次实例化创建的唯一实例指针。

class SingleClass
{
public:
	static SingleClass* createInstance( void )
    {
	    if (m_Instance== NULL)
	    {
		    m_Instance= new SingleClass();
		    return m_Instance;
	    }
	    else
		    return m_Instance;
    }
private:
	SingleClass(){};
	~SingleClass(){};
private:
	static SingleClass* m_Instance;
};

二、日志小系统设计

        鉴于以上的要求,计划设计一个单体类CLogger,该类具备独立线程能力,采用一个队列来接收来自各个线程、各个功能模块的日志推送,CLogger实例负责将加入到队列的日志,添加一些附加信息后,逐条记录在文件中。

        为了区分日志事件,通过枚举创建日志类型;针对日志运行是一个长期的过程,不可能全部存储在一个日志文件中,因此指定一个文件目录,并按天来存放.

log_test
    bin              #编译目标输出目录
    build_win        #win编译文件及中间文件目录
    build_linux      #Linux编译文件及中间文件目录
    src              #源码
        log.h        #单体实例,继承自定义基类MyThread线程类
        log.cpp
        Mutex.h
        Mutex.cpp
        myThread.h    #linux下线程类实现
        myThread.cpp
        win32Thread.h #win下线程类实现   
        win32Thread.cpp    
    CMakeLists.txt   #cmake工程配置文件

log.h:

#ifndef CHANNELLOG_H
#define CHANNELLOG_H

#include <stdio.h>
#include <stdarg.h>
#include <string>

#ifdef WIN32
#include "win32Thread.h"
#endif

#ifdef linux
#include "myThread.h"
#include <string.h>
#endif

#include <queue>
#include "Mutex.h"

enum eLogType 
{
	eHardError		= 1,
	eSoftError		= 2,
	eConfigError	= 3,
	eParameterError = 4,
	eReadError		= 5,
	eWriteError		= 6,
	eControlMessage = 7,
	eResponseMessage = 8,
	eTipMessage		= 9
};

struct MyLogStruct
{
	MyLogStruct():type(0)
	{
		memset(szBuffer, 0, 1024);
	};
	int type;
	char   szBuffer[1024];
};

class CLogger : public MyThread
{
public:
	void Log(const eLogType type, const char* lpszFormat, ...);
	static CLogger* createInstance( void );
private:
	CLogger();
	~CLogger();
	int Run();
	bool getFirstLog(MyLogStruct &it);
	void addLog(MyLogStruct it);
private:
	static CLogger* m_pLogInstance;
	bool running;
	//for cache
	std::queue<MyLogStruct> mylogs;
	PYMutex m_Mutex_Log;
	int i_log_over;
};
#endif

log.cpp

#include "Log.h"
#include <time.h>
#include <sys/timeb.h>

#ifdef __linux__
#ifndef sprintf_s
#define sprintf_s sprintf
#endif
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#else
#include <windows.h>
#endif

namespace GlobalVar
{
	std::string logdir = "log";		//后期记录日志目录重配置文件读取
	std::string logname = "pyfree";	//可设置为服务名
};

CLogger* CLogger::m_pLogInstance = NULL;

void WriteLogToFile( std::string strPath,std::string strTime,const char * strMsg)
{
	FILE * fpLog = NULL;
	//open
#ifdef WIN32
	fopen_s(&fpLog, strPath.c_str(), "a+");
#else
	fpLog = fopen(strPath.c_str(), "a+");
#endif
	if (NULL != fpLog)
	{
		fseek(fpLog, 0, SEEK_END);
		fwrite(strTime.c_str(), strTime.length(), 1, fpLog);
		fwrite(strMsg, strlen(strMsg), 1, fpLog);
		fwrite("\n", 1, 1, fpLog);
		fclose(fpLog);
	}
}

std::string createFileName(std::string strTime_)
{
	//file name
	std::string strPath = GlobalVar::logdir;	
#ifdef WIN32
	strPath += "\\";
#else
	strPath += "/";
#endif
	strPath += strTime_;
	strPath += "_";
	strPath += GlobalVar::logname;
	strPath += ".log";
	return strPath;
}

std::string strTime()
{
	time_t tt;
	struct timeb tm0;
	struct tm tm1;
	char buf[512];

	//time(&tt);
	ftime(&tm0);
	tt = tm0.time;
#ifdef WIN32
	localtime_s(&tm1, &tt);
#else
	localtime_r(&tt, &tm1);
#endif
	sprintf_s(buf, "%04d-%02d-%02d %02d:%02d:%02d.%03d "
		, tm1.tm_year + 1900, tm1.tm_mon + 1, tm1.tm_mday
		, tm1.tm_hour, tm1.tm_min, tm1.tm_sec, tm0.millitm);
	std::string strTime_ = buf;
	buf[10] = '\0';
	return strTime_;
}

std::string createLogTimeAndTypeDesc(const int iMsgType, std::string strTime_)
{
	std::string strTimeAndTypeDesc = strTime_;
	//
	switch (iMsgType)
	{
	case eHardError:
		strTimeAndTypeDesc += "[HardErrorIMsg] ";
		break;
	case eSoftError:
		strTimeAndTypeDesc += "[SoftErrorIMsg] ";
		break;
	case eConfigError:
		strTimeAndTypeDesc += "[ConfErrorIMsg] ";
		break;
	case eParameterError:
		strTimeAndTypeDesc += "[ParamErrorMsg] ";
		break;
	case eReadError:
		strTimeAndTypeDesc += "[ReadErrorIMsg] ";
		break;
	case eWriteError:
		strTimeAndTypeDesc += "[WriteErrorMsg] ";
		break;
	case eControlMessage:
		strTimeAndTypeDesc += "[ControlExeMsg] ";
		break;
	case eResponseMessage:
		strTimeAndTypeDesc += "[ResponseUpMsg] ";
		break;
	case eTipMessage:
		strTimeAndTypeDesc += "[NoticeTipIMsg] ";
		break;
	default:
		strTimeAndTypeDesc += "[PromptUnNoMsg] ";
		break;
	}	
	return strTimeAndTypeDesc;
}

void WriteLog( const int iMsgType, const char * strMsg)
{
	try {
		//system time to string
		std::string strTime_ = strTime();
		//file name
		std::string strPath = createFileName(strTime_.substr(0,10));	
		// log time and type desc
		std::string strTimeAndTypeDesc = createLogTimeAndTypeDesc(iMsgType, strTime_);
		WriteLogToFile(strPath,strTimeAndTypeDesc,strMsg);
	}
	catch (...) {
		printf("write log[%d]{%s}error\n", iMsgType, strMsg);
	}
}

CLogger::CLogger() 
	: running(true)
	, i_log_over(0)
{
	char buf[256] = {0};
	sprintf_s(buf,"mkdir %s",GlobalVar::logdir.c_str());
	system(buf);
	this->start();
};

CLogger::~CLogger()
{
	running = false;
};

CLogger* CLogger::createInstance( void )
{
	if (m_pLogInstance == NULL)
	{
		m_pLogInstance = new CLogger();
		return m_pLogInstance;
	}
	else
		return m_pLogInstance;
};

int CLogger::Run()
{
	MyLogStruct log_;
	while (running) {
		if (getFirstLog(log_))
		{
			WriteLog(log_.type, log_.szBuffer);
#ifndef WIN32
			printf("Log::[%d]-->%s\n", getpid(), log_.szBuffer);
#else
			printf("Log::-->%s\n", log_.szBuffer);
#endif
		}
#ifdef WIN32
		Sleep(10);
#else
		usleep(10000);
#endif
	}
	return 0;
};

void CLogger::Log(const eLogType type, const char* lpszFormat, ...)
{
	va_list args;
	//char   szBuffer[2048] = {0};
	MyLogStruct log_;
	log_.type = static_cast<int>(type);
	va_start(args, lpszFormat);
#ifdef WIN32
	vsnprintf_s(log_.szBuffer,sizeof(log_.szBuffer), lpszFormat, args);
#else
	vsnprintf(log_.szBuffer, sizeof(log_.szBuffer), lpszFormat, args);
#endif
	va_end(args); 
	addLog(log_);
}

bool CLogger::getFirstLog(MyLogStruct &it) {
	bool ret = false;
	m_Mutex_Log.Lock();
	if (!mylogs.empty()) {
		it = mylogs.front();
		mylogs.pop();
		ret = true;
	}
	m_Mutex_Log.Unlock();
	return ret;
}

void CLogger::addLog(MyLogStruct it) {
	m_Mutex_Log.Lock();
	if (mylogs.size() > 100) {
		i_log_over++;
		mylogs.pop();
	}
	mylogs.push(it);
	m_Mutex_Log.Unlock();
	if (i_log_over >= 100) {//每溢出100次,报告一次
		MyLogStruct log_;
		log_.type = static_cast<int>(eTipMessage);
		sprintf(log_.szBuffer,"the size of mylogs queue is up to limmit size[100],[%s %s %d]."
			, __FILE__, __FUNCTION__, __LINE__);
		m_Mutex_Log.Lock();
		mylogs.push(log_);
		m_Mutex_Log.Unlock();
		i_log_over = 0;
	}
}

Mutex.h

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#ifndef PYMUTEX_H
#define PYMUTEX_H

#ifdef WIN32
//#include <windows.h>
#else
#include <pthread.h>
#endif

typedef void *HANDLE;

class IMutex
{
public:
	virtual ~IMutex() {}

	virtual void Lock() const = 0;
	virtual bool TryLock() const = 0;
	virtual void Unlock() const = 0;
};

class PYMutex : public IMutex
{
public:
	PYMutex();
	~PYMutex();

	virtual void Lock() const;
	virtual bool TryLock() const;
	virtual void Unlock() const;
private:
#ifdef _WIN32
	HANDLE m_mutex;
#else
	mutable pthread_mutex_t m_mutex;
#endif
};

#endif //PYMUTEX_H

Mutex.cpp

#include "Mutex.h"

#ifdef WIN32
#include <windows.h>
#endif
#include <stdio.h>

PYMutex::PYMutex()
{
#ifdef _WIN32
	m_mutex = ::CreateMutex(NULL, FALSE, NULL);
#else
	pthread_mutex_init(&m_mutex, NULL);
#endif
}


PYMutex::~PYMutex()
{
#ifdef _WIN32
	::CloseHandle(m_mutex);
#else
	pthread_mutex_destroy(&m_mutex);
#endif
}


void PYMutex::Lock() const
{
#ifdef _WIN32
	WaitForSingleObject(m_mutex, INFINITE);
#else
	pthread_mutex_lock(&m_mutex);
#endif
}

bool PYMutex::TryLock() const
{
#ifdef _WIN32
    DWORD dwWaitResult = WaitForSingleObject(m_mutex, 0);  
	if (dwWaitResult != WAIT_OBJECT_0 && dwWaitResult != WAIT_TIMEOUT) {
		printf("thread WARNING: bad result from try-locking mutex\n");
	}
    return (dwWaitResult == WAIT_OBJECT_0) ? true : false; 
#else
	return (0==pthread_mutex_trylock(&m_mutex))?true:false;
#endif	
};

void PYMutex::Unlock() const
{
#ifdef _WIN32
	::ReleaseMutex(m_mutex);
#else
	pthread_mutex_unlock(&m_mutex);
#endif
}

linux下myThread.h

/*
 * add arg in linux system and compile: -lpthread
 *
 */
#ifndef _MYTHREAD_H
#define _MYTHREAD_H

#include <pthread.h>
#include <unistd.h>

class MyThread
{
private:
    //current thread ID
    pthread_t tid;
    //thread status
    int threadStatus;
    //get manner pointer of execution 
    static void* run0(void* pVoid);
    //manner of execution inside
    void* run1();
public:
    //threadStatus-new create
    static const int THREAD_STATUS_NEW = 0;
    //threadStatus-running
    static const int THREAD_STATUS_RUNNING = 1;
    //threadStatus-end
    static const int THREAD_STATUS_EXIT = -1;
    // constructed function
    MyThread();
    ~MyThread();
    //the entity for thread running
    virtual int Run()=0;
    //start thread
    bool start();
    //gte thread ID
    pthread_t getThreadID();
    //get thread status
    int getState();
    //wait for thread end
    void join();
    //wait for thread end in limit time
    void join(unsigned long millisTime);
};

#endif /* _MYTHREAD_H */

linux下myThread.cpp

#include "myThread.h"

#include <stdio.h>

void* MyThread::run0(void* pVoid)
{
    MyThread* p = (MyThread*) pVoid;
    p->run1();
    return p;
}

void* MyThread::run1()
{
    threadStatus = THREAD_STATUS_RUNNING;
    tid = pthread_self();
    Run();
    threadStatus = THREAD_STATUS_EXIT;
    tid = 0;
    pthread_exit(NULL);
}

MyThread::MyThread()
{
    tid = 0;
    threadStatus = THREAD_STATUS_NEW;
}

MyThread::~MyThread()
{
	join(10);
}

int MyThread::Run()
{
    while(true){
        printf("thread is running!\n");
        sleep(100);
    }
    return 0;
}

bool MyThread::start()
{
    return pthread_create(&tid, NULL, run0, this) == 0;
}

pthread_t MyThread::getThreadID()
{
    return tid;
}

int MyThread::getState()
{
    return threadStatus;
}

void MyThread::join()
{
    if (tid > 0)
    {
        pthread_join(tid, NULL);
    }
}

void MyThread::join(unsigned long millisTime)
{
    if (tid == 0)
    {
        return;
    }
    if (millisTime == 0)
    {
        join();
    }else
    {
        unsigned long k = 0;
        while (threadStatus != THREAD_STATUS_EXIT && k <= millisTime)
        {
            usleep(100);
            k++;
        }
    }
}
 

win下win32Thread.h

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000
#ifndef WIN32THREAD_H
#define WIN32THREAD_H

#include <process.h>
#include <iostream>

typedef void *HANDLE;
class MyThread
{
public:
	MyThread();
	~MyThread();
	void start();
	virtual int Run();
	HANDLE getThread();
private:
	HANDLE hThread;
	static void agent(void *p);
};
#endif

win下win32Thread.cpp

#include "win32Thread.h"

#include <windows.h>

MyThread::MyThread()
{
}

MyThread::~MyThread()
{
	WaitForSingleObject(hThread, INFINITE);
}

void MyThread::start()
{
    hThread =(HANDLE)_beginthread(agent, 0, (void *)this);
}
int MyThread::Run()
{
	printf("Base Thread\n");
	return 0;
}
void MyThread::agent(void *p)
{
    MyThread *agt = (MyThread *)p;
    agt->Run();
}
HANDLE MyThread::getThread()
{
    return hThread;
}

 main.cpp,展示如何使用log类

#include <stdio.h>
#include <stdlib.h>

#ifdef WIN32
#include <windows.h>
#endif
#include "Log.h"

char LOG_FILE_NAME[128] = "pyfree_log";
std::string logdir = "./log";
namespace GlobalVar {
	extern std::string logname;
};

int main(int argc, char *argv[])
{
	#ifdef WIN32
	std::string appname = std::string(argv[0]);
	GlobalVar::logname = appname.substr(0,appname.length()-4);
	#else
	GlobalVar::logname = std::string(argv[0]);
	#endif
	int i = 0;
	while(i<1)
	{
		CLogger::createInstance()->Log(eLogType(rand()%10),"log test for %d\n",i++);
		#ifdef WIN32
		Sleep(10);
		#else
		usleep(10000);
		#endif
	}
	return 0;
}

三、编译及展示

        CMakeLists.txt:

# CMake 最低版本号要求
cmake_minimum_required (VERSION 2.8)
# 项目信息
project (log_test)
#
if(WIN32)
    message(STATUS "windows compiling...")
    add_definitions(-D_PLATFORM_IS_WINDOWS_)
	set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
  set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd")
  set(WIN_OS true)
else(WIN32)
    message(STATUS "linux compiling...")
    add_definitions( -D_PLATFORM_IS_LINUX_)
    add_definitions("-Wno-invalid-source-encoding")
	  # add_definitions("-O2")
    set(UNIX_OS true)
    set(_DEBUG true)
    
endif(WIN32)

#
set(EXECUTABLE_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/bin)

# 指定源文件的目录,并将名称保存到变量
SET(source_h
    #
	${PROJECT_SOURCE_DIR}/src/Mutex.h
	${PROJECT_SOURCE_DIR}/src/log.h
  )
  
SET(source_cpp
    #
	${PROJECT_SOURCE_DIR}/src/Mutex.cpp
	${PROJECT_SOURCE_DIR}/src/log.cpp
	${PROJECT_SOURCE_DIR}/src/main.cpp
  )
  
#头文件目录
include_directories(${PROJECT_SOURCE_DIR}/include)

if (${UNIX_OS})

SET(source_h_linux
    #
	${PROJECT_SOURCE_DIR}/src/myThread.h
  )
  
SET(source_cpp_linux
  ${PROJECT_SOURCE_DIR}/src/myThread.cpp
)

add_definitions(
  "-W"
  "-fPIC"
  "-Wall"
  # "-Wall -g"
  "-Werror"
  "-Wshadow"
  "-Wformat"
  "-Wpointer-arith"
  "-D_REENTRANT"
  "-D_USE_FAST_MACRO"
  "-Wno-long-long"
  "-Wuninitialized"
  "-D_POSIX_PTHREAD_SEMANTICS"
  "-DACL_PREPARE_COMPILE"
  "-Wno-unused-parameter"
  "-fexceptions"
  )
  set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -O0")

link_directories()
# 指定生成目标
add_executable(log_test ${source_h} ${source_cpp} ${source_h_linux} ${source_cpp_linux})
#link
target_link_libraries(log_test 
  -lpthread -pthread -lz -lrt -ldl
)

endif(${UNIX_OS})

if (${WIN_OS})

SET(source_h_win
  ${PROJECT_SOURCE_DIR}/src/win32Thread.h
)

SET(source_cpp_win
  ${PROJECT_SOURCE_DIR}/src/win32Thread.cpp
)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /wd4819")

add_definitions(
  "-D_CRT_SECURE_NO_WARNINGS"
  "-D_WINSOCK_DEPRECATED_NO_WARNINGS"
  "-DNO_WARN_MBCS_MFC_DEPRECATION"
  "-DWIN32_LEAN_AND_MEAN"
)

link_directories()

if (CMAKE_BUILD_TYPE STREQUAL "Debug")

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_DEBUG ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(log_testd ${source_h} ${source_cpp} ${source_h_win} ${source_cpp_win})

else(CMAKE_BUILD_TYPE)

set(CMAKE_RUNTIME_OUTPUT_DIRECTORY_RELEASE ${PROJECT_SOURCE_DIR}/bin)
# 指定生成目标
add_executable(log_test ${source_h} ${source_cpp} ${source_h_win} ${source_cpp_win})

endif (CMAKE_BUILD_TYPE)

endif(${WIN_OS})

        Linux下

        cd log_test
        mkdir build_linux
        cd build_linux
        cmake ..
        make

编译及执行过程

[py@pyfree build_linux]$ cmake ..
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Check for working C compiler: /usr/bin/cc
-- Check for working C compiler: /usr/bin/cc -- works
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Detecting C compile features
-- Detecting C compile features - done
-- Check for working CXX compiler: /usr/bin/c++
-- Check for working CXX compiler: /usr/bin/c++ -- works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- linux compiling...
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/hgfs/workForMy/workspace/log_test/build_linux
[py@pyfree build_linux]$ make
Scanning dependencies of target log_test
[ 20%] Building CXX object CMakeFiles/log_test.dir/src/Mutex.cpp.o
[ 40%] Building CXX object CMakeFiles/log_test.dir/src/log.cpp.o
[ 60%] Building CXX object CMakeFiles/log_test.dir/src/main.cpp.o
[ 80%] Building CXX object CMakeFiles/log_test.dir/src/myThread.cpp.o
[100%] Linking CXX executable ../bin/log_test
[100%] Built target log_test
[py@pyfree build_linux]$ 

[py@pyfree bin]$ ./log_test
mkdir: 无法创建目录"log": 文件已存在
Log::[5153]-->log test for 0

[py@pyfree bin]$ 

win编译时采用cmake+vs编译,具体编译版本可以依据自身电脑安装版本决定

cd log_test && mkdir build_win && cd build_win
cmake -G "Visual Studio 10 2010 Win64" -DCMAKE_BUILD_TYPE=Release ..
msbuild log_test.sln /p:Configuration="Release" /p:Platform="x64"

 省略编译过程,结果展示如下:

四、完整代码目录:

文案中已经提供完整代码信息,如果还是不知道如何组织项目代码,请这里下载:

https://download.csdn.net/download/py8105/86548527

相关文章:

  • 【Django】开发日报_4_Day:手机号码管理系统(1)
  • Oracle中含替换变量的查询(二)
  • 论文解读:Sadeepcry:使用自我注意和自动编码器网络的蛋白质结晶倾向预测的深度学习框架
  • Android Framework 框架层 | AMS 定义与知识点梳理
  • 工业场景全流程!机器学习开发并部署服务到云端
  • 真无线耳机哪个好?真无线耳机性价比排行榜
  • IDL学习:语法基础-程序控制
  • 市场调研团体怎么使用无人系统生产更安全
  • [ 常用工具篇 ] AntSword 蚁剑安装及使用详解
  • 百度校园招聘历年经典面试题汇总:测试开发
  • 支撑Java NIO 与 NodeJS的底层技术
  • vue2+element 通用表格组件封装
  • Symbol Table
  • LeetCode所有大于等于节点的值之和
  • 基站天线交叉极化比测量的不确定度评定
  • [deviceone开发]-do_Webview的基本示例
  • [分享]iOS开发 - 实现UITableView Plain SectionView和table不停留一起滑动
  • 5、React组件事件详解
  • Angular6错误 Service: No provider for Renderer2
  • Apache Zeppelin在Apache Trafodion上的可视化
  • ES6 ...操作符
  • es6--symbol
  • exif信息对照
  • iOS编译提示和导航提示
  • linux学习笔记
  • October CMS - 快速入门 9 Images And Galleries
  • OSS Web直传 (文件图片)
  • Promise面试题2实现异步串行执行
  • 彻底搞懂浏览器Event-loop
  • 仿天猫超市收藏抛物线动画工具库
  • 海量大数据大屏分析展示一步到位:DataWorks数据服务+MaxCompute Lightning对接DataV最佳实践...
  • 实现菜单下拉伸展折叠效果demo
  • 温故知新之javascript面向对象
  • 线性表及其算法(java实现)
  • ​LeetCode解法汇总518. 零钱兑换 II
  • (12)目标检测_SSD基于pytorch搭建代码
  • (16)UiBot:智能化软件机器人(以头歌抓取课程数据为例)
  • (八)Spring源码解析:Spring MVC
  • (论文阅读笔记)Network planning with deep reinforcement learning
  • (三)uboot源码分析
  • (原創) 如何優化ThinkPad X61開機速度? (NB) (ThinkPad) (X61) (OS) (Windows)
  • (转)C语言家族扩展收藏 (转)C语言家族扩展
  • (转)linux自定义开机启动服务和chkconfig使用方法
  • ****** 二十三 ******、软设笔记【数据库】-数据操作-常用关系操作、关系运算
  • **CI中自动类加载的用法总结
  • 、写入Shellcode到注册表上线
  • .equals()到底是什么意思?
  • .net 获取url的方法
  • .NET/C# 解压 Zip 文件时出现异常:System.IO.InvalidDataException: 找不到中央目录结尾记录。
  • .netcore 如何获取系统中所有session_如何把百度推广中获取的线索(基木鱼,电话,百度商桥等)同步到企业微信或者企业CRM等企业营销系统中...
  • .Net转Java自学之路—基础巩固篇十三(集合)
  • .考试倒计时43天!来提分啦!
  • @GetMapping和@RequestMapping的区别
  • @TableId注解详细介绍 mybaits 实体类主键注解
  • @Transactional注解下,循环取序列的值,但得到的值都相同的问题